Compare commits

..

No commits in common. "main" and "compilation-benchmark" have entirely different histories.

250 changed files with 7023 additions and 9573 deletions

View file

@ -1,52 +1,49 @@
---
kind: pipeline kind: pipeline
name: amd64 name: default
platform:
os: linux
arch: amd64
steps: steps:
- name: fetch git submodules
image: node:15-alpine3.12
commands:
- apk add git
- git submodule update --init --recursive --remote
- find docs/
- name: chown repo - name: chown repo
image: ekidd/rust-musl-builder:1.50.0 image: ekidd/rust-musl-builder:1.47.0
user: root user: root
commands: commands:
- chown 1000:1000 . -R - chown 1000:1000 . -R
- name: check documentation build
image: ekidd/rust-musl-builder:1.47.0
commands:
- cargo install mdbook --git https://github.com/Ruin0x11/mdBook.git --branch localization --rev d06249b
- mdbook build docs/
- name: check formatting - name: check formatting
image: rustdocker/rust:nightly image: rustdocker/rust:nightly
commands: commands:
- /root/.cargo/bin/cargo fmt -- --check - /root/.cargo/bin/cargo fmt -- --check
- name: cargo clippy - name: cargo clippy
image: ekidd/rust-musl-builder:1.50.0 image: ekidd/rust-musl-builder:1.47.0
environment:
CARGO_HOME: /drone/src/.cargo
commands: commands:
- whoami
- ls -la ~/.cargo
- mv ~/.cargo .
- ls -la .cargo
- cargo clippy --workspace --tests --all-targets --all-features -- -D warnings -D deprecated -D clippy::perf -D clippy::complexity -D clippy::dbg_macro - cargo clippy --workspace --tests --all-targets --all-features -- -D warnings -D deprecated -D clippy::perf -D clippy::complexity -D clippy::dbg_macro
- cargo clippy --workspace -- -D clippy::unwrap_used
- name: cargo test - name: cargo test
image: ekidd/rust-musl-builder:1.50.0 image: ekidd/rust-musl-builder:1.47.0
environment: environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
RUST_TEST_THREADS: 1 RUST_TEST_THREADS: 1
CARGO_HOME: /drone/src/.cargo
commands: commands:
- sudo apt-get update - sudo apt-get update
- sudo apt-get -y install --no-install-recommends espeak postgresql-client - sudo apt-get -y install --no-install-recommends espeak postgresql-client
- cargo test --workspace --no-fail-fast - cargo test --workspace --no-fail-fast
- name: cargo build - name: cargo build
image: ekidd/rust-musl-builder:1.50.0 image: ekidd/rust-musl-builder:1.47.0
environment:
CARGO_HOME: /drone/src/.cargo
commands: commands:
- cargo build - cargo build
- mv target/x86_64-unknown-linux-musl/debug/lemmy_server target/lemmy_server - mv target/x86_64-unknown-linux-musl/debug/lemmy_server target/lemmy_server
@ -57,12 +54,21 @@ steps:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432 LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432
DO_WRITE_HOSTS_FILE: 1 DO_WRITE_HOSTS_FILE: 1
commands: commands:
- ls -la target/lemmy_server
- apk add bash curl postgresql-client - apk add bash curl postgresql-client
- bash api_tests/prepare-drone-federation-test.sh - bash api_tests/prepare-drone-federation-test.sh
- cd api_tests/ - cd api_tests/
- yarn - yarn
- yarn api-test - yarn api-test
- name: create docker tags
image: ekidd/rust-musl-builder:1.47.0
commands:
- echo "$(git describe),latest" > .tags
when:
ref:
- refs/tags/*
- name: make release build and push to docker hub - name: make release build and push to docker hub
image: plugins/docker image: plugins/docker
settings: settings:
@ -73,24 +79,6 @@ steps:
from_secret: docker_password from_secret: docker_password
repo: dessalines/lemmy repo: dessalines/lemmy
auto_tag: true auto_tag: true
auto_tag_suffix: linux-amd64
when:
ref:
- refs/tags/*
- name: push to docker manifest
image: plugins/manifest
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
target: "dessalines/lemmy:${DRONE_TAG}"
template: "dessalines/lemmy:${DRONE_TAG}-OS-ARCH"
platforms:
- linux/amd64
- linux/arm64
ignore_missing: true
when: when:
ref: ref:
- refs/tags/* - refs/tags/*
@ -102,89 +90,6 @@ services:
POSTGRES_USER: lemmy POSTGRES_USER: lemmy
POSTGRES_PASSWORD: password POSTGRES_PASSWORD: password
--- volumes:
kind: pipeline - name: dieselcli
name: arm64 temp: {}
platform:
os: linux
arch: arm64
steps:
- name: cargo test
image: rust:1.50-slim-buster
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: 1
RUST_TEST_THREADS: 1
CARGO_HOME: /drone/src/.cargo
commands:
- apt-get update
- apt-get -y install --no-install-recommends espeak postgresql-client libssl-dev pkg-config libpq-dev
- cargo test --workspace --no-fail-fast
- cargo build
# Using Debian here because there seems to be no official Alpine-based Rust docker image for ARM.
- name: cargo build
image: rust:1.50-slim-buster
environment:
CARGO_HOME: /drone/src/.cargo
commands:
- apt-get update
- apt-get -y install --no-install-recommends libssl-dev pkg-config libpq-dev
- cargo build
- mv target/debug/lemmy_server target/lemmy_server
- name: run federation tests
image: node:15-buster-slim
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432
DO_WRITE_HOSTS_FILE: 1
commands:
- mkdir -p /usr/share/man/man1 /usr/share/man/man7
- apt-get update
- apt-get -y install --no-install-recommends bash curl libssl-dev pkg-config libpq-dev postgresql-client libc6-dev
- bash api_tests/prepare-drone-federation-test.sh
- cd api_tests/
- yarn
- yarn api-test
- name: make release build and push to docker hub
image: plugins/docker
settings:
dockerfile: docker/prod/Dockerfile.arm
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: dessalines/lemmy
auto_tag: true
auto_tag_suffix: linux-arm64
when:
ref:
- refs/tags/*
- name: push to docker manifest
image: plugins/manifest
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
target: "dessalines/lemmy:${DRONE_TAG}"
template: "dessalines/lemmy:${DRONE_TAG}-OS-ARCH"
platforms:
- linux/amd64
- linux/arm64
ignore_missing: true
when:
ref:
- refs/tags/*
services:
- name: database
image: postgres:12-alpine
environment:
POSTGRES_USER: lemmy
POSTGRES_PASSWORD: password

4
.gitmodules vendored Normal file
View file

@ -0,0 +1,4 @@
[submodule "docs"]
path = docs
url = https://github.com/LemmyNet/lemmy-docs
branch = main

View file

@ -1 +0,0 @@
*.sqldump

View file

@ -1,5 +1,5 @@
tab_spaces = 2 tab_spaces = 2
edition="2018" edition="2018"
imports_layout="HorizontalVertical" imports_layout="HorizontalVertical"
imports_granularity="Crate" merge_imports=true
reorder_imports=true reorder_imports=true

35
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,35 @@
# Code of Conduct
- We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic.
- Please avoid using overtly sexual aliases or other nicknames that might detract from a friendly, safe and welcoming environment for all.
- Please be kind and courteous. Theres no need to be mean or rude.
- Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
- Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.
- We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term “harassment” as including the definition in the Citizen Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we dont tolerate behavior that excludes people in socially marginalized groups.
- Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the Lemmy moderation team immediately. Whether youre a regular contributor or a newcomer, we care about making this community a safe place for you and weve got your back.
- Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
[**Message the Moderation Team on Mastodon**](https://mastodon.social/@LemmyDev)
[**Email The Moderation Team**](mailto:contact@lemmy.ml)
## Moderation
These are the policies for upholding our communitys standards of conduct. If you feel that a thread needs moderation, please contact the Lemmy moderation team .
1. Remarks that violate the Lemmy standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.)
2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed.
3. Moderators will first respond to such remarks with a warning, at the same time the offending content will likely be removed whenever possible.
4. If the warning is unheeded, the user will be “kicked,” i.e., kicked out of the communication channel to cool off.
5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded.
6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology.
7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, in private. Complaints about bans in-channel are not allowed.
8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others.
In the Lemmy community we strive to go the extra step to look out for each other. Dont just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if theyre off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely.
And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you couldve communicated better — remember that its your responsibility to make others comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.
The enforcement policies listed above apply to all official Lemmy venues; including git repositories under [github.com/LemmyNet/lemmy](https://github.com/LemmyNet/lemmy) and [yerbamate.ml/LemmyNet/lemmy](https://yerbamate.ml/LemmyNet/lemmy), the [Matrix channel](https://matrix.to/#/!BZVTUuEiNmRcbFeLeI:matrix.org?via=matrix.org&via=privacytools.io&via=permaweb.io); and all instances under lemmy.ml. For other projects adopting the Rust Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.
Adapted from the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct), which is based on the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).

View file

@ -1,4 +1,4 @@
# Contributing # Contributing
See [here](https://join.lemmy.ml/docs/en/contributing/contributing.html) for contributing Instructions. See [here](https://lemmy.ml/docs/contributing.html) for contributing Instructions.

1082
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,58 +3,61 @@ name = "lemmy_server"
version = "0.0.1" version = "0.0.1"
edition = "2018" edition = "2018"
[lib]
doctest = false
[profile.dev] [profile.dev]
debug = 0 debug = 0
[profile.release]
lto = true
[workspace] [workspace]
members = [ members = [
"crates/api", "lemmy_api",
"crates/apub", "lemmy_apub",
"crates/utils", "lemmy_utils",
"crates/db_queries", "lemmy_db_queries",
"crates/db_schema", "lemmy_db_schema",
"crates/db_views", "lemmy_db_views",
"crates/db_views_actor", "lemmy_db_views_actor",
"crates/db_views_actor", "lemmy_db_views_actor",
"crates/api_structs", "lemmy_structs",
"crates/websocket", "lemmy_websocket",
"crates/routes"
] ]
[dependencies] [dependencies]
lemmy_api = { path = "./crates/api" } lemmy_api = { path = "./lemmy_api" }
lemmy_apub = { path = "./crates/apub" } lemmy_apub = { path = "./lemmy_apub" }
lemmy_utils = { path = "./crates/utils" } lemmy_utils = { path = "./lemmy_utils" }
lemmy_db_schema = { path = "./crates/db_schema" } lemmy_db_schema = { path = "./lemmy_db_schema" }
lemmy_db_queries = { path = "./crates/db_queries" } lemmy_db_queries = { path = "lemmy_db_queries" }
lemmy_db_views = { path = "./crates/db_views" } lemmy_db_views = { path = "./lemmy_db_views" }
lemmy_db_views_moderator = { path = "./crates/db_views_moderator" } lemmy_db_views_moderator = { path = "./lemmy_db_views_moderator" }
lemmy_db_views_actor = { path = "./crates/db_views_actor" } lemmy_db_views_actor = { path = "lemmy_db_views_actor" }
lemmy_api_structs = { path = "crates/api_structs" } lemmy_structs = { path = "./lemmy_structs" }
lemmy_websocket = { path = "./crates/websocket" } lemmy_websocket = { path = "./lemmy_websocket" }
lemmy_routes = { path = "./crates/routes" }
diesel = "1.4.5" diesel = "1.4.5"
diesel_migrations = "1.4.0" diesel_migrations = "1.4.0"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.118", features = ["derive"] }
actix = "0.10.0" actix = "0.10.0"
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] } actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
log = "0.4.14" actix-files = { version = "0.4.1", default-features = false }
actix-web-actors = { version = "3.0.0", default-features = false }
awc = { version = "2.0.3", default-features = false }
log = "0.4.11"
env_logger = "0.8.2" env_logger = "0.8.2"
strum = "0.20.0" strum = "0.20.0"
url = { version = "2.2.1", features = ["serde"] } lazy_static = "1.4.0"
openssl = "0.10.32" rss = "1.9.0"
url = { version = "2.2.0", features = ["serde"] }
openssl = "0.10.31"
http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] } http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
tokio = "0.3.6" tokio = "0.3.6"
anyhow = "1.0.38" sha2 = "0.9.2"
anyhow = "1.0.36"
reqwest = { version = "0.10.10", features = ["json"] } reqwest = { version = "0.10.10", features = ["json"] }
activitystreams = "0.7.0-alpha.10" activitystreams = "0.7.0-alpha.8"
actix-rt = { version = "1.1.1", default-features = false } actix-rt = { version = "1.1.1", default-features = false }
serde_json = { version = "1.0.61", features = ["preserve_order"] } serde_json = { version = "1.0.60", features = ["preserve_order"] }
clokwerk = "0.3.4"
[dev-dependencies.cargo-husky] [dev-dependencies.cargo-husky]
version = "1.5.0" version = "1.5.0"

View file

@ -1,13 +1,12 @@
<div align="center"> <div align="center">
![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/LemmyNet/lemmy.svg) ![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/LemmyNet/lemmy.svg)
[![Build Status](https://cloud.drone.io/api/badges/LemmyNet/lemmy/status.svg)](https://cloud.drone.io/LemmyNet/lemmy/) [![Build Status](https://travis-ci.org/LemmyNet/lemmy.svg?branch=main)](https://travis-ci.org/LemmyNet/lemmy)
[![GitHub issues](https://img.shields.io/github/issues-raw/LemmyNet/lemmy.svg)](https://github.com/LemmyNet/lemmy/issues) [![GitHub issues](https://img.shields.io/github/issues-raw/LemmyNet/lemmy.svg)](https://github.com/LemmyNet/lemmy/issues)
[![Docker Pulls](https://img.shields.io/docker/pulls/dessalines/lemmy.svg)](https://cloud.docker.com/repository/docker/dessalines/lemmy/) [![Docker Pulls](https://img.shields.io/docker/pulls/dessalines/lemmy.svg)](https://cloud.docker.com/repository/docker/dessalines/lemmy/)
[![Translation status](http://weblate.yerbamate.ml/widgets/lemmy/-/lemmy/svg-badge.svg)](http://weblate.yerbamate.ml/engage/lemmy/) [![Translation status](http://weblate.yerbamate.ml/widgets/lemmy/-/lemmy/svg-badge.svg)](http://weblate.yerbamate.ml/engage/lemmy/)
[![License](https://img.shields.io/github/license/LemmyNet/lemmy.svg)](LICENSE) [![License](https://img.shields.io/github/license/LemmyNet/lemmy.svg)](LICENSE)
![GitHub stars](https://img.shields.io/github/stars/LemmyNet/lemmy?style=social) ![GitHub stars](https://img.shields.io/github/stars/LemmyNet/lemmy?style=social)
[![Awesome Humane Tech](https://raw.githubusercontent.com/humanetech-community/awesome-humane-tech/main/humane-tech-badge.svg?sanitize=true)](https://github.com/humanetech-community/awesome-humane-tech)
</div> </div>
<p align="center"> <p align="center">
@ -21,23 +20,21 @@
<br /> <br />
<a href="https://join.lemmy.ml">Join Lemmy</a> <a href="https://join.lemmy.ml">Join Lemmy</a>
· ·
<a href="https://join.lemmy.ml/docs/en/index.html">Documentation</a> <a href="https://lemmy.ml/docs/index.html">Documentation</a>
· ·
<a href="https://github.com/LemmyNet/lemmy/issues">Report Bug</a> <a href="https://github.com/LemmyNet/lemmy/issues">Report Bug</a>
· ·
<a href="https://github.com/LemmyNet/lemmy/issues">Request Feature</a> <a href="https://github.com/LemmyNet/lemmy/issues">Request Feature</a>
· ·
<a href="https://github.com/LemmyNet/lemmy/blob/main/RELEASES.md">Releases</a> <a href="https://github.com/LemmyNet/lemmy/blob/main/RELEASES.md">Releases</a>
·
<a href="https://join.lemmy.ml/docs/en/code_of_conduct.html">Code of Conduct</a>
</p> </p>
</p> </p>
## About The Project ## About The Project
Desktop|Mobile Front Page|Post
---|--- ---|---
![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/static/images/main_img.webp)|![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/static/images/mobile_pic.webp) ![main screen](https://raw.githubusercontent.com/LemmyNet/lemmy/main/docs/img/main_screen.png)|![chat screen](https://raw.githubusercontent.com/LemmyNet/lemmy/main/docs/img/chat_screen.png)
[Lemmy](https://github.com/LemmyNet/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse). [Lemmy](https://github.com/LemmyNet/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
@ -47,7 +44,7 @@ The overall goal is to create an easily self-hostable, decentralized alternative
Each Lemmy server can set its own moderation policy; appointing site-wide admins, and community moderators to keep out the trolls, and foster a healthy, non-toxic environment where all can feel comfortable contributing. Each Lemmy server can set its own moderation policy; appointing site-wide admins, and community moderators to keep out the trolls, and foster a healthy, non-toxic environment where all can feel comfortable contributing.
*Note: The WebSocket and HTTP APIs are currently unstable* *Note: Federation is still in active development and the WebSocket, as well as, HTTP API are currently unstable*
### Why's it called Lemmy? ### Why's it called Lemmy?
@ -68,7 +65,7 @@ Each Lemmy server can set its own moderation policy; appointing site-wide admins
- Open source, [AGPL License](/LICENSE). - Open source, [AGPL License](/LICENSE).
- Self hostable, easy to deploy. - Self hostable, easy to deploy.
- Comes with [Docker](https://join.lemmy.ml/docs/en/administration/install_docker.html) and [Ansible](https://join.lemmy.ml/docs/en/administration/install_ansible.html). - Comes with [Docker](https://lemmy.ml/docs/administration_install_docker.html) and [Ansible](https://lemmy.ml/docs/administration_install_ansible.html).
- Clean, mobile-friendly interface. - Clean, mobile-friendly interface.
- Only a minimum of a username and password is required to sign up! - Only a minimum of a username and password is required to sign up!
- User avatar support. - User avatar support.
@ -103,16 +100,16 @@ Each Lemmy server can set its own moderation policy; appointing site-wide admins
## Installation ## Installation
- [Docker](https://join.lemmy.ml/docs/en/administration/install_docker.html) - [Docker](https://lemmy.ml/docs/administration_install_docker.html)
- [Ansible](https://join.lemmy.ml/docs/en/administration/install_ansible.html) - [Ansible](https://lemmy.ml/docs/administration_install_ansible.html)
## Lemmy Projects ## Lemmy Projects
### Apps ### Apps
- [lemmy-ui - The official web app for lemmy](https://github.com/LemmyNet/lemmy-ui) - [lemmy-ui - The official web app for lemmy](https://github.com/LemmyNet/lemmy-ui)
- [Lemmur - A mobile client for Lemmy (Android, Linux, Windows)](https://github.com/krawieck/lemmur) - [Lemmur - A flutter lemmy app ( under development )](https://github.com/krawieck/lemmur)
- [Remmel - A native iOS app](https://github.com/uuttff8/Lemmy-iOS) - [Lemmy-mobile (Android / IOS) - React native ( under development )](https://github.com/koredefashokun/lemmy-mobile)
### Libraries ### Libraries
@ -137,13 +134,13 @@ Lemmy is free, open-source software, meaning no advertising, monetizing, or vent
## Contributing ## Contributing
- [Contributing instructions](https://join.lemmy.ml/docs/en/contributing/contributing.html) - [Contributing instructions](https://lemmy.ml/docs/contributing.html)
- [Docker Development](https://join.lemmy.ml/docs/en/contributing/docker_development.html) - [Docker Development](https://lemmy.ml/docs/contributing_docker_development.html)
- [Local Development](https://join.lemmy.ml/docs/en/contributing/local_development.html) - [Local Development](https://lemmy.ml/docs/contributing_local_development.html)
### Translations ### Translations
If you want to help with translating, take a look at [Weblate](https://weblate.yerbamate.ml/projects/lemmy/). You can also help by [translating the documentation](https://github.com/LemmyNet/lemmy-docs#adding-a-new-language). If you want to help with translating, take a look at [Weblate](https://weblate.yerbamate.ml/projects/lemmy/).
## Contact ## Contact

View file

@ -1,126 +1,3 @@
# Lemmy v0.9.9 Release (2021-02-19)
## Changes
### Lemmy backend
- Added an federated activity query sorting order.
- Explicitly marking posts and comments as public.
- Added a `NewComment` / forum sort for posts.
- Fixed an issue with not setting correct published time for fetched posts.
- Fixed an issue with an open docker port on lemmy-ui.
- Using lemmy post link for RSS link.
- Fixed reason and display name lengths to use char counts instead.
### Lemmy-ui
- Updated translations.
- Made websocket host configurable.
- Added some accessibility features.
- Always showing password reset link.
# Lemmy v0.9.7 Release (2021-02-08)
## Changes
- Posts and comments are no longer live-sorted (meaning most content should stay in place).
- Fixed an issue with the create post title field not expanding when copied from iframely suggestion.
- Fixed broken federated community paging / sorting.
- Added aria attributes for accessibility, thx to @Mitch Lillie.
- Updated translations and added croatian.
- No changes to lemmy back-end.
# Lemmy v0.9.6 Release (2021-02-05)
## Changes
- Fixed inbox_urls not being correctly set, which broke federation in `v0.9.5`. Added some logging to catch these.
- Fixing community search not using auth.
- Moved docs to https://join.lemmy.ml
- Fixed an issue w/ lemmy-ui with forms being cleared out.
# Lemmy v0.9.4 Pre-Release (2021-02-02)
## Changes
### Lemmy
- Fixed a critical bug with votes and comment unlike responses not being `0` for your user.
- Fixed a critical bug with comment creation not checking if its parent comment is in the post.
- Serving proper activities for community outbox.
- Added some active user counts, including `users_active_day`, `users_active_week`, `users_active_month`, `users_active_half_year` to `SiteAggregates` and `CommunityAggregates`. (Also added to lemmy-ui)
- Made sure banned users can't follow.
- Added `FederatedInstances` to `SiteResponse`, to show allowed and blocked instances. (Also added to lemmy-ui)
- Added a `MostComments` sort for posts. (Also added to lemmy-ui)
### Lemmy-UI
- Added a scroll position restore to lemmy-ui.
- Reworked the combined inbox so incoming comments don't wipe out your current form.
- Fixed an updated bug on the user page.
- Fixed cross-post titles and body getting clipped.
- Fixing the post creation title height.
- Squashed some other smaller bugs.
# Lemmy v0.9.0 Release (2021-01-25)
## Changes
Since our last release in October of last year, and we've had [~450](https://github.com/LemmyNet/lemmy/compare/v0.8.0...main) commits.
The biggest changes, as we'll outline below, are a re-work of Lemmy's database structure, a `v2` of Lemmy's API, and activitypub compliance fixes. The new re-worked DB is much faster, easier to maintain, and [now supports hierarchical rather than flat objects in the new API](https://github.com/LemmyNet/lemmy/issues/1275).
We've also seen the first release of [Lemmur](https://github.com/krawieck/lemmur/releases/tag/v0.1.1), an android / iOS (soon) / windows / linux client, as well as [Lemmer](https://github.com/uuttff8/Lemmy-iOS), a native iOS client. Much thanks to @krawieck, @shilangyu, and @uuttff8 for making these great clients. If you can, please contribute to their [patreon](https://www.patreon.com/lemmur) to help fund lemmur development.
## LemmyNet projects
### Lemmy Server
- [Moved views from SQL to Diesel](https://github.com/LemmyNet/lemmy/issues/1275). This was a spinal replacement for much of lemmy.
- Removed all the old fast_tables and triggers, and created new aggregates tables.
- Added a `v2` of the API to support the hierarchical objects created from the above changes.
- Moved continuous integration to [drone](https://cloud.drone.io/LemmyNet/lemmy/), now includes formatting, clippy, and cargo build checks, unit testing, and federation testing. [Drone also deploys both amd64 and arm64 images to dockerhub.](https://hub.docker.com/r/dessalines/lemmy)
- Split out documentation into git submodule.
- Shortened slur filter to avoid false positives.
- Added query performance testing and comparisons. Added indexes to make sure every query is `< 30 ms`.
- Added compilation time testing.
### Federation
This release includes some bug fixes for federation, and some changes to get us closer to compliance with the ActivityPub standard.
- [Community bans now federating](https://github.com/LemmyNet/lemmy/issues/1287).
- [Local posts sometimes got marked as remote](https://github.com/LemmyNet/lemmy/issues/1302).
- [Creator of post/comment was not notified about new child comments](https://github.com/LemmyNet/lemmy/issues/1325).
- [Community deletion now federated](https://github.com/LemmyNet/lemmy/issues/1256).
None of these are breaking changes, so federation between 0.9.0 and 0.8.11 will work without problems.
### Lemmy javascript / typescript client
- Updated the [lemmy-js-client](https://github.com/LemmyNet/lemmy-js-client) to use the new `v2` API. Our API docs now reference this project's files, to show what the http / websocket forms and responses should look like.
- Drone now handles publishing its [npm packages.](https://www.npmjs.com/package/lemmy-js-client)
### Lemmy-UI
- Updated it to use the `v2` API via `lemmy-js-client`, required changing nearly every component.
- Added a live comment count.
- Added drone deploying, and builds for ARM.
- Fixed community link wrapping.
- Various other bug fixes.
### Lemmy Docs
- We moved documentation into a separate git repository, and support translation for the docs now!
- Moved our code of conduct into the documentation.
## Upgrading
If you'd like to make a DB backup before upgrading, follow [this guide](https://join.lemmy.ml/docs/en/administration/backup_and_restore.html).
- [Upgrade with manual Docker installation](https://join.lemmy.ml/docs/en/administration/install_docker.html#updating)
- [Upgrade with Ansible installation](https://join.lemmy.ml/docs/en/administration/install_ansible.html)
# Lemmy v0.8.0 Release (2020-10-16) # Lemmy v0.8.0 Release (2020-10-16)
## Changes ## Changes
@ -143,7 +20,7 @@ Here are some of the bigger changes:
- The first **federation public beta release**, woohoo :fireworks: - The first **federation public beta release**, woohoo :fireworks:
- All Lemmy functionality now works over ActivityPub (except turning remote users into mods/admins) - All Lemmy functionality now works over ActivityPub (except turning remote users into mods/admins)
- Instance allowlist and blocklist - Instance allowlist and blocklist
- Documentation for [admins](https://join.lemmy.ml/docs/administration_federation.html) and [devs](https://join.lemmy.ml/docs/contributing_federation_overview.html) on how federation works - Documentation for [admins](https://lemmy.ml/docs/administration_federation.html) and [devs](https://lemmy.ml/docs/contributing_federation_overview.html) on how federation works
- Upgraded to newest versions of @asonix activitypub libraries - Upgraded to newest versions of @asonix activitypub libraries
- Full local federation setup for manual testing - Full local federation setup for manual testing
- Automated testing for nearly every federation action - Automated testing for nearly every federation action
@ -181,8 +58,8 @@ We'd also like to thank both the [NLnet foundation](https://nlnet.nl/) for their
## Upgrading ## Upgrading
- [with manual Docker installation](https://join.lemmy.ml/docs/administration_install_docker.html#updating) - [with manual Docker installation](https://lemmy.ml/docs/administration_install_docker.html#updating)
- [with Ansible installation](https://join.lemmy.ml/docs/administration_install_ansible.html) - [with Ansible installation](https://lemmy.ml/docs/administration_install_ansible.html)
## Testing Federation ## Testing Federation
@ -270,7 +147,7 @@ Overall, since our last major release in January (v0.6.0), we have closed over
Before starting the upgrade, make sure that you have a working backup of your Before starting the upgrade, make sure that you have a working backup of your
database and image files. See our database and image files. See our
[documentation](https://join.lemmy.ml/docs/administration_backup_and_restore.html) [documentation](https://lemmy.ml/docs/administration_backup_and_restore.html)
for backup instructions. for backup instructions.
**With Ansible:** **With Ansible:**

View file

@ -1 +1 @@
0.10.0-rc.7 0.9.0-rc.4

View file

@ -64,14 +64,6 @@
- src: '../docker/iframely.config.local.js' - src: '../docker/iframely.config.local.js'
dest: '{{lemmy_base_dir}}/iframely.config.local.js' dest: '{{lemmy_base_dir}}/iframely.config.local.js'
mode: '0600' mode: '0600'
vars:
lemmy_docker_image: "dessalines/lemmy:dev"
lemmy_docker_ui_image: "dessalines/lemmy-ui:{{ lookup('file', 'VERSION') }}"
lemmy_port: "8536"
lemmy_ui_port: "1235"
pictshare_port: "8537"
iframely_port: "8538"
postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}"
- name: add config file (only during initial setup) - name: add config file (only during initial setup)
template: template:

View file

@ -1,6 +1,6 @@
{ {
# for more info about the config, check out the documentation # for more info about the config, check out the documentation
# https://join.lemmy.ml/docs/en/administration/configuration.html # https://lemmy.ml/docs/administration_configuration.html
# settings related to the postgresql database # settings related to the postgresql database
database: { database: {
@ -26,12 +26,11 @@
# whether to enable activitypub federation. # whether to enable activitypub federation.
enabled: false enabled: false
# Allows and blocks are described here: # Allows and blocks are described here:
# https://join.lemmy.ml/docs/en/federation/administration.html#instance-allowlist-and-blocklist # https://lemmy.ml/docs/administration_federation.html#instance-allowlist-and-blocklist
# #
# comma separated list of instances with which federation is allowed # comma separated list of instances with which federation is allowed
# Only one of these blocks should be uncommented # allowed_instances: ""
# allowed_instances: ["instance1.tld","instance2.tld"]
# comma separated list of instances which are blocked from federating # comma separated list of instances which are blocked from federating
# blocked_instances: [] # blocked_instances: ""
} }
} }

View file

@ -1,4 +1,4 @@
version: '2.2' version: '3.3'
services: services:
lemmy: lemmy:
@ -38,14 +38,13 @@ services:
restart: always restart: always
pictrs: pictrs:
image: asonix/pictrs:v0.2.6-r1 image: asonix/pictrs:v0.2.5-r0
user: 991:991 user: 991:991
ports: ports:
- "127.0.0.1:8537:8080" - "127.0.0.1:8537:8080"
volumes: volumes:
- ./volumes/pictrs:/mnt - ./volumes/pictrs:/mnt
restart: always restart: always
mem_limit: 200m
iframely: iframely:
image: dogbin/iframely:latest image: dogbin/iframely:latest
@ -54,7 +53,6 @@ services:
volumes: volumes:
- ./iframely.config.local.js:/iframely/config.local.js:ro - ./iframely.config.local.js:/iframely/config.local.js:ro
restart: always restart: always
mem_limit: 200m
postfix: postfix:
image: mwader/postfix-relay image: mwader/postfix-relay

View file

@ -78,7 +78,7 @@ server {
} }
# backend # backend
location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) { location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
proxy_pass http://0.0.0.0:{{ lemmy_port }}; proxy_pass http://0.0.0.0:{{ lemmy_port }};
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;

View file

@ -12,14 +12,14 @@
"api-test": "jest src/ -i --verbose" "api-test": "jest src/ -i --verbose"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^26.0.20", "@types/jest": "^26.0.19",
"eslint": "^7.18.0",
"eslint-plugin-jane": "^9.0.3",
"jest": "^26.6.3", "jest": "^26.6.3",
"lemmy-js-client": "0.10.0-rc.4", "lemmy-js-client": "1.0.17-beta6",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"prettier": "^2.1.2",
"ts-jest": "^26.4.4", "ts-jest": "^26.4.4",
"prettier": "^2.1.2",
"eslint": "^7.10.0",
"eslint-plugin-jane": "^9.0.3",
"typescript": "^4.1.3" "typescript": "^4.1.3"
} }
} }

View file

@ -1,6 +1,13 @@
#!/bin/bash #!/bin/bash
set -e set -e
export LEMMY_JWT_SECRET=changeme
export LEMMY_FEDERATION__ENABLED=true
export LEMMY_TLS_ENABLED=false
export LEMMY_SETUP__ADMIN_PASSWORD=lemmy
export LEMMY_RATE_LIMIT__POST=99999
export LEMMY_RATE_LIMIT__REGISTER=99999
export LEMMY_CAPTCHA__ENABLED=false
export LEMMY_TEST_SEND_SYNC=1 export LEMMY_TEST_SEND_SYNC=1
export RUST_BACKTRACE=1 export RUST_BACKTRACE=1
@ -28,40 +35,52 @@ fi
killall lemmy_server || true killall lemmy_server || true
echo "$PWD"
echo "start alpha" echo "start alpha"
LEMMY_HOSTNAME=lemmy-alpha:8541 \ LEMMY_HOSTNAME=lemmy-alpha:8541 \
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_alpha.hjson \ LEMMY_PORT=8541 \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_alpha" \ LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_alpha" \
LEMMY_HOSTNAME="lemmy-alpha:8541" \ LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon \
target/lemmy_server >/tmp/lemmy_alpha.out 2>&1 & LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha \
LEMMY_SETUP__SITE_NAME=lemmy-alpha \
target/lemmy_server >/dev/null 2>&1 &
echo "start beta" echo "start beta"
LEMMY_HOSTNAME=lemmy-beta:8551 \ LEMMY_HOSTNAME=lemmy-beta:8551 \
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_beta.hjson \ LEMMY_PORT=8551 \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_beta" \ LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_beta" \
target/lemmy_server >/tmp/lemmy_beta.out 2>&1 & LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon \
LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta \
LEMMY_SETUP__SITE_NAME=lemmy-beta \
target/lemmy_server >/dev/null 2>&1 &
echo "start gamma" echo "start gamma"
LEMMY_HOSTNAME=lemmy-gamma:8561 \ LEMMY_HOSTNAME=lemmy-gamma:8561 \
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_gamma.hjson \ LEMMY_PORT=8561 \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_gamma" \ LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_gamma" \
target/lemmy_server >/tmp/lemmy_gamma.out 2>&1 & LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon \
LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma \
LEMMY_SETUP__SITE_NAME=lemmy-gamma \
target/lemmy_server >/dev/null 2>&1 &
echo "start delta" echo "start delta"
# An instance with only an allowlist for beta # An instance with only an allowlist for beta
LEMMY_HOSTNAME=lemmy-delta:8571 \ LEMMY_HOSTNAME=lemmy-delta:8571 \
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_delta.hjson \ LEMMY_PORT=8571 \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_delta" \ LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_delta" \
target/lemmy_server >/tmp/lemmy_delta.out 2>&1 & LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta \
LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta \
LEMMY_SETUP__SITE_NAME=lemmy-delta \
target/lemmy_server >/dev/null 2>&1 &
echo "start epsilon" echo "start epsilon"
# An instance who has a blocklist, with lemmy-alpha blocked # An instance who has a blocklist, with lemmy-alpha blocked
LEMMY_HOSTNAME=lemmy-epsilon:8581 \ LEMMY_HOSTNAME=lemmy-epsilon:8581 \
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_epsilon.hjson \ LEMMY_PORT=8581 \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_epsilon" \ LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_epsilon" \
target/lemmy_server >/tmp/lemmy_epsilon.out 2>&1 & LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha \
LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon \
LEMMY_SETUP__SITE_NAME=lemmy-epsilon \
target/lemmy_server >/dev/null 2>&1 &
echo "wait for all instances to start" echo "wait for all instances to start"
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8541/api/v2/site')" != "200" ]]; do sleep 1; done while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8541/api/v2/site')" != "200" ]]; do sleep 1; done

View file

@ -4,7 +4,7 @@ set -e
export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432 export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432
pushd .. pushd ..
cargo build cargo +1.47.0 build
rm target/lemmy_server || true rm target/lemmy_server || true
cp target/debug/lemmy_server target/lemmy_server cp target/debug/lemmy_server target/lemmy_server
./api_tests/prepare-drone-federation-test.sh ./api_tests/prepare-drone-federation-test.sh

View file

@ -33,6 +33,9 @@ function assertCommunityFederation(
); );
expect(communityOne.creator.actor_id).toBe(communityTwo.creator.actor_id); expect(communityOne.creator.actor_id).toBe(communityTwo.creator.actor_id);
expect(communityOne.community.nsfw).toBe(communityTwo.community.nsfw); expect(communityOne.community.nsfw).toBe(communityTwo.community.nsfw);
expect(communityOne.community.category_id).toBe(
communityTwo.community.category_id
);
expect(communityOne.community.removed).toBe(communityTwo.community.removed); expect(communityOne.community.removed).toBe(communityTwo.community.removed);
expect(communityOne.community.deleted).toBe(communityTwo.community.deleted); expect(communityOne.community.deleted).toBe(communityTwo.community.deleted);
} }

View file

@ -20,9 +20,9 @@ import {
getPost, getPost,
unfollowRemotes, unfollowRemotes,
searchForUser, searchForUser,
banPersonFromSite, banUserFromSite,
searchPostLocal, searchPostLocal,
banPersonFromCommunity, banUserFromCommunity,
} from './shared'; } from './shared';
import { PostView, CommunityView } from 'lemmy-js-client'; import { PostView, CommunityView } from 'lemmy-js-client';
@ -305,7 +305,7 @@ test('Enforce site ban for federated user', async () => {
expect(alphaUser).toBeDefined(); expect(alphaUser).toBeDefined();
// ban alpha from beta site // ban alpha from beta site
let banAlpha = await banPersonFromSite(beta, alphaUser.person.id, true); let banAlpha = await banUserFromSite(beta, alphaUser.user.id, true);
expect(banAlpha.banned).toBe(true); expect(banAlpha.banned).toBe(true);
// Alpha makes post on beta // Alpha makes post on beta
@ -321,7 +321,7 @@ test('Enforce site ban for federated user', async () => {
expect(betaPost).toBeUndefined(); expect(betaPost).toBeUndefined();
// Unban alpha // Unban alpha
let unBanAlpha = await banPersonFromSite(beta, alphaUser.person.id, false); let unBanAlpha = await banUserFromSite(beta, alphaUser.user.id, false);
expect(unBanAlpha.banned).toBe(false); expect(unBanAlpha.banned).toBe(false);
}); });
@ -332,8 +332,8 @@ test('Enforce community ban for federated user', async () => {
expect(alphaUser).toBeDefined(); expect(alphaUser).toBeDefined();
// ban alpha from beta site // ban alpha from beta site
await banPersonFromCommunity(beta, alphaUser.person.id, 2, false); await banUserFromCommunity(beta, alphaUser.user.id, 2, false);
let banAlpha = await banPersonFromCommunity(beta, alphaUser.person.id, 2, true); let banAlpha = await banUserFromCommunity(beta, alphaUser.user.id, 2, true);
expect(banAlpha.banned).toBe(true); expect(banAlpha.banned).toBe(true);
// Alpha makes post on beta // Alpha makes post on beta
@ -349,9 +349,9 @@ test('Enforce community ban for federated user', async () => {
expect(betaPost).toBeUndefined(); expect(betaPost).toBeUndefined();
// Unban alpha // Unban alpha
let unBanAlpha = await banPersonFromCommunity( let unBanAlpha = await banUserFromCommunity(
beta, beta,
alphaUser.person.id, alphaUser.user.id,
2, 2,
false false
); );

View file

@ -25,7 +25,7 @@ import {
CreateCommunity, CreateCommunity,
DeleteCommunity, DeleteCommunity,
RemoveCommunity, RemoveCommunity,
GetPersonMentions, GetUserMentions,
CreateCommentLike, CreateCommentLike,
CreatePostLike, CreatePostLike,
EditPrivateMessage, EditPrivateMessage,
@ -36,15 +36,15 @@ import {
GetPost, GetPost,
PrivateMessageResponse, PrivateMessageResponse,
PrivateMessagesResponse, PrivateMessagesResponse,
GetPersonMentionsResponse, GetUserMentionsResponse,
SaveUserSettings, SaveUserSettings,
SortType, SortType,
ListingType, ListingType,
GetSiteResponse, GetSiteResponse,
SearchType, SearchType,
LemmyHttp, LemmyHttp,
BanPersonResponse, BanUserResponse,
BanPerson, BanUser,
BanFromCommunity, BanFromCommunity,
BanFromCommunityResponse, BanFromCommunityResponse,
Post, Post,
@ -144,7 +144,7 @@ export async function editPost(api: API, post: Post): Promise<PostResponse> {
let name = 'A jest test federated post, updated'; let name = 'A jest test federated post, updated';
let form: EditPost = { let form: EditPost = {
name, name,
post_id: post.id, edit_id: post.id,
auth: api.auth, auth: api.auth,
nsfw: false, nsfw: false,
}; };
@ -157,7 +157,7 @@ export async function deletePost(
post: Post post: Post
): Promise<PostResponse> { ): Promise<PostResponse> {
let form: DeletePost = { let form: DeletePost = {
post_id: post.id, edit_id: post.id,
deleted: deleted, deleted: deleted,
auth: api.auth, auth: api.auth,
}; };
@ -170,7 +170,7 @@ export async function removePost(
post: Post post: Post
): Promise<PostResponse> { ): Promise<PostResponse> {
let form: RemovePost = { let form: RemovePost = {
post_id: post.id, edit_id: post.id,
removed, removed,
auth: api.auth, auth: api.auth,
}; };
@ -183,7 +183,7 @@ export async function stickyPost(
post: Post post: Post
): Promise<PostResponse> { ): Promise<PostResponse> {
let form: StickyPost = { let form: StickyPost = {
post_id: post.id, edit_id: post.id,
stickied, stickied,
auth: api.auth, auth: api.auth,
}; };
@ -196,7 +196,7 @@ export async function lockPost(
post: Post post: Post
): Promise<PostResponse> { ): Promise<PostResponse> {
let form: LockPost = { let form: LockPost = {
post_id: post.id, edit_id: post.id,
locked, locked,
auth: api.auth, auth: api.auth,
}; };
@ -289,32 +289,32 @@ export async function searchForUser(
return api.client.search(form); return api.client.search(form);
} }
export async function banPersonFromSite( export async function banUserFromSite(
api: API, api: API,
person_id: number, user_id: number,
ban: boolean ban: boolean
): Promise<BanPersonResponse> { ): Promise<BanUserResponse> {
// Make sure lemmy-beta/c/main is cached on lemmy_alpha // Make sure lemmy-beta/c/main is cached on lemmy_alpha
// Use short-hand search url // Use short-hand search url
let form: BanPerson = { let form: BanUser = {
person_id, user_id,
ban, ban,
remove_data: false, remove_data: false,
auth: api.auth, auth: api.auth,
}; };
return api.client.banPerson(form); return api.client.banUser(form);
} }
export async function banPersonFromCommunity( export async function banUserFromCommunity(
api: API, api: API,
person_id: number, user_id: number,
community_id: number, community_id: number,
ban: boolean ban: boolean
): Promise<BanFromCommunityResponse> { ): Promise<BanFromCommunityResponse> {
// Make sure lemmy-beta/c/main is cached on lemmy_alpha // Make sure lemmy-beta/c/main is cached on lemmy_alpha
// Use short-hand search url // Use short-hand search url
let form: BanFromCommunity = { let form: BanFromCommunity = {
person_id, user_id,
community_id, community_id,
remove_data: false, remove_data: false,
ban, ban,
@ -376,12 +376,12 @@ export async function createComment(
export async function editComment( export async function editComment(
api: API, api: API,
comment_id: number, edit_id: number,
content = 'A jest test federated comment update' content = 'A jest test federated comment update'
): Promise<CommentResponse> { ): Promise<CommentResponse> {
let form: EditComment = { let form: EditComment = {
content, content,
comment_id, edit_id,
auth: api.auth, auth: api.auth,
}; };
return api.client.editComment(form); return api.client.editComment(form);
@ -390,10 +390,10 @@ export async function editComment(
export async function deleteComment( export async function deleteComment(
api: API, api: API,
deleted: boolean, deleted: boolean,
comment_id: number edit_id: number
): Promise<CommentResponse> { ): Promise<CommentResponse> {
let form: DeleteComment = { let form: DeleteComment = {
comment_id, edit_id,
deleted, deleted,
auth: api.auth, auth: api.auth,
}; };
@ -403,23 +403,23 @@ export async function deleteComment(
export async function removeComment( export async function removeComment(
api: API, api: API,
removed: boolean, removed: boolean,
comment_id: number edit_id: number
): Promise<CommentResponse> { ): Promise<CommentResponse> {
let form: RemoveComment = { let form: RemoveComment = {
comment_id, edit_id,
removed, removed,
auth: api.auth, auth: api.auth,
}; };
return api.client.removeComment(form); return api.client.removeComment(form);
} }
export async function getMentions(api: API): Promise<GetPersonMentionsResponse> { export async function getMentions(api: API): Promise<GetUserMentionsResponse> {
let form: GetPersonMentions = { let form: GetUserMentions = {
sort: SortType.New, sort: SortType.New,
unread_only: false, unread_only: false,
auth: api.auth, auth: api.auth,
}; };
return api.client.getPersonMentions(form); return api.client.getUserMentions(form);
} }
export async function likeComment( export async function likeComment(
@ -448,6 +448,7 @@ export async function createCommunity(
description, description,
icon, icon,
banner, banner,
category_id: 1,
nsfw: false, nsfw: false,
auth: api.auth, auth: api.auth,
}; };
@ -467,10 +468,10 @@ export async function getCommunity(
export async function deleteCommunity( export async function deleteCommunity(
api: API, api: API,
deleted: boolean, deleted: boolean,
community_id: number edit_id: number
): Promise<CommunityResponse> { ): Promise<CommunityResponse> {
let form: DeleteCommunity = { let form: DeleteCommunity = {
community_id, edit_id,
deleted, deleted,
auth: api.auth, auth: api.auth,
}; };
@ -480,10 +481,10 @@ export async function deleteCommunity(
export async function removeCommunity( export async function removeCommunity(
api: API, api: API,
removed: boolean, removed: boolean,
community_id: number edit_id: number
): Promise<CommunityResponse> { ): Promise<CommunityResponse> {
let form: RemoveCommunity = { let form: RemoveCommunity = {
community_id, edit_id,
removed, removed,
auth: api.auth, auth: api.auth,
}; };
@ -505,12 +506,12 @@ export async function createPrivateMessage(
export async function editPrivateMessage( export async function editPrivateMessage(
api: API, api: API,
private_message_id: number edit_id: number
): Promise<PrivateMessageResponse> { ): Promise<PrivateMessageResponse> {
let updatedContent = 'A jest test federated private message edited'; let updatedContent = 'A jest test federated private message edited';
let form: EditPrivateMessage = { let form: EditPrivateMessage = {
content: updatedContent, content: updatedContent,
private_message_id, edit_id,
auth: api.auth, auth: api.auth,
}; };
return api.client.editPrivateMessage(form); return api.client.editPrivateMessage(form);
@ -519,11 +520,11 @@ export async function editPrivateMessage(
export async function deletePrivateMessage( export async function deletePrivateMessage(
api: API, api: API,
deleted: boolean, deleted: boolean,
private_message_id: number edit_id: number
): Promise<PrivateMessageResponse> { ): Promise<PrivateMessageResponse> {
let form: DeletePrivateMessage = { let form: DeletePrivateMessage = {
deleted, deleted,
private_message_id, edit_id,
auth: api.auth, auth: api.auth,
}; };
return api.client.deletePrivateMessage(form); return api.client.deletePrivateMessage(form);
@ -537,6 +538,7 @@ export async function registerUser(
username, username,
password: 'test', password: 'test',
password_verify: 'test', password_verify: 'test',
admin: false,
show_nsfw: true, show_nsfw: true,
}; };
return api.client.register(form); return api.client.register(form);

View file

@ -8,7 +8,7 @@ import {
getSite, getSite,
} from './shared'; } from './shared';
import { import {
PersonViewSafe, UserViewSafe,
SaveUserSettings, SaveUserSettings,
SortType, SortType,
ListingType, ListingType,
@ -17,14 +17,14 @@ import {
let auth: string; let auth: string;
let apShortname: string; let apShortname: string;
function assertUserFederation(userOne: PersonViewSafe, userTwo: PersonViewSafe) { function assertUserFederation(userOne: UserViewSafe, userTwo: UserViewSafe) {
expect(userOne.person.name).toBe(userTwo.person.name); expect(userOne.user.name).toBe(userTwo.user.name);
expect(userOne.person.preferred_username).toBe(userTwo.person.preferred_username); expect(userOne.user.preferred_username).toBe(userTwo.user.preferred_username);
expect(userOne.person.bio).toBe(userTwo.person.bio); expect(userOne.user.bio).toBe(userTwo.user.bio);
expect(userOne.person.actor_id).toBe(userTwo.person.actor_id); expect(userOne.user.actor_id).toBe(userTwo.user.actor_id);
expect(userOne.person.avatar).toBe(userTwo.person.avatar); expect(userOne.user.avatar).toBe(userTwo.user.avatar);
expect(userOne.person.banner).toBe(userTwo.person.banner); expect(userOne.user.banner).toBe(userTwo.user.banner);
expect(userOne.person.published).toBe(userTwo.person.published); expect(userOne.user.published).toBe(userTwo.user.published);
} }
test('Create user', async () => { test('Create user', async () => {
@ -34,7 +34,7 @@ test('Create user', async () => {
let site = await getSite(alpha, auth); let site = await getSite(alpha, auth);
expect(site.my_user).toBeDefined(); expect(site.my_user).toBeDefined();
apShortname = `@${site.my_user.person.name}@lemmy-alpha:8541`; apShortname = `@${site.my_user.name}@lemmy-alpha:8541`;
}); });
test('Set some user settings, check that they are federated', async () => { test('Set some user settings, check that they are federated', async () => {

View file

@ -293,10 +293,10 @@
exec-sh "^0.3.2" exec-sh "^0.3.2"
minimist "^1.2.0" minimist "^1.2.0"
"@eslint/eslintrc@^0.3.0": "@eslint/eslintrc@^0.2.2":
version "0.3.0" version "0.2.2"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.2.tgz#d01fc791e2fc33e88a29d6f3dc7e93d0cd784b76"
integrity sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg== integrity sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==
dependencies: dependencies:
ajv "^6.12.4" ajv "^6.12.4"
debug "^4.1.1" debug "^4.1.1"
@ -305,7 +305,7 @@
ignore "^4.0.6" ignore "^4.0.6"
import-fresh "^3.2.1" import-fresh "^3.2.1"
js-yaml "^3.13.1" js-yaml "^3.13.1"
lodash "^4.17.20" lodash "^4.17.19"
minimatch "^3.0.4" minimatch "^3.0.4"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
@ -590,7 +590,7 @@
dependencies: dependencies:
"@types/istanbul-lib-report" "*" "@types/istanbul-lib-report" "*"
"@types/jest@26.x": "@types/jest@26.x", "@types/jest@^26.0.19":
version "26.0.19" version "26.0.19"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.19.tgz#e6fa1e3def5842ec85045bd5210e9bb8289de790" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.19.tgz#e6fa1e3def5842ec85045bd5210e9bb8289de790"
integrity sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ== integrity sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ==
@ -598,14 +598,6 @@
jest-diff "^26.0.0" jest-diff "^26.0.0"
pretty-format "^26.0.0" pretty-format "^26.0.0"
"@types/jest@^26.0.20":
version "26.0.20"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.20.tgz#cd2f2702ecf69e86b586e1f5223a60e454056307"
integrity sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==
dependencies:
jest-diff "^26.0.0"
pretty-format "^26.0.0"
"@types/json-schema@^7.0.3": "@types/json-schema@^7.0.3":
version "7.0.6" version "7.0.6"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
@ -1833,13 +1825,13 @@ eslint-visitor-keys@^2.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
eslint@^7.18.0: eslint@^7.10.0:
version "7.18.0" version "7.17.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.18.0.tgz#7fdcd2f3715a41fe6295a16234bd69aed2c75e67" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.17.0.tgz#4ccda5bf12572ad3bf760e6f195886f50569adb0"
integrity sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ== integrity sha512-zJk08MiBgwuGoxes5sSQhOtibZ75pz0J35XTRlZOk9xMffhpA9BTbQZxoXZzOl5zMbleShbGwtw+1kGferfFwQ==
dependencies: dependencies:
"@babel/code-frame" "^7.0.0" "@babel/code-frame" "^7.0.0"
"@eslint/eslintrc" "^0.3.0" "@eslint/eslintrc" "^0.2.2"
ajv "^6.10.0" ajv "^6.10.0"
chalk "^4.0.0" chalk "^4.0.0"
cross-spawn "^7.0.2" cross-spawn "^7.0.2"
@ -1863,7 +1855,7 @@ eslint@^7.18.0:
js-yaml "^3.13.1" js-yaml "^3.13.1"
json-stable-stringify-without-jsonify "^1.0.1" json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.4.1" levn "^0.4.1"
lodash "^4.17.20" lodash "^4.17.19"
minimatch "^3.0.4" minimatch "^3.0.4"
natural-compare "^1.4.0" natural-compare "^1.4.0"
optionator "^0.9.1" optionator "^0.9.1"
@ -3233,10 +3225,10 @@ language-tags@^1.0.5:
dependencies: dependencies:
language-subtag-registry "~0.3.2" language-subtag-registry "~0.3.2"
lemmy-js-client@0.10.0-rc.4: lemmy-js-client@1.0.17-beta6:
version "0.10.0-rc.4" version "1.0.17-beta6"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.10.0-rc.4.tgz#ac6fe6940fc5f73260ddb166ce0ef3c0520901fc" resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-1.0.17-beta6.tgz#afe1e1da13172a161c4d976b1ee58fe81eb22829"
integrity sha512-yJPnvGaWneOOwjKEqb4qXtQk+4DbRgO+hEzSin2GgUgnxluY43gemwiCPt6EnV+j4ueKoi0+QORVg2RuRC2PaQ== integrity sha512-+oX7J7wht8nH4a5NQngK1GNner3TDv6ZOhQQVI5KcK7vynVVIcgveC5KBJArHBAl5acXpLs3Khmx0ZEb+sErJA==
leven@^3.1.0: leven@^3.1.0:
version "3.1.0" version "3.1.0"

View file

@ -35,6 +35,8 @@
tls_enabled: true tls_enabled: true
# json web token for authorization between server and client # json web token for authorization between server and client
jwt_secret: "changeme" jwt_secret: "changeme"
# path to built documentation
docs_dir: "/app/documentation"
# address where pictrs is available # address where pictrs is available
pictrs_url: "http://pictrs:8080" pictrs_url: "http://pictrs:8080"
# address where iframely is available # address where iframely is available
@ -63,13 +65,12 @@
# whether to enable activitypub federation. # whether to enable activitypub federation.
enabled: false enabled: false
# Allows and blocks are described here: # Allows and blocks are described here:
# https://join.lemmy.ml/docs/en/federation/administration.html#instance-allowlist-and-blocklist # https://lemmy.ml/docs/administration_federation.html#instance-allowlist-and-blocklist
# #
# comma separated list of instances with which federation is allowed # comma separated list of instances with which federation is allowed
# Only one of these blocks should be uncommented allowed_instances: ""
# allowed_instances: ["instance1.tld","instance2.tld"]
# comma separated list of instances which are blocked from federating # comma separated list of instances which are blocked from federating
# blocked_instances: [] blocked_instances: ""
} }
captcha: { captcha: {
enabled: true enabled: true

View file

@ -1,50 +0,0 @@
[package]
name = "lemmy_api"
version = "0.1.0"
edition = "2018"
[lib]
name = "lemmy_api"
path = "src/lib.rs"
doctest = false
[dependencies]
lemmy_apub = { path = "../apub" }
lemmy_utils = { path = "../utils" }
lemmy_db_queries = { path = "../db_queries" }
lemmy_db_schema = { path = "../db_schema" }
lemmy_db_views = { path = "../db_views" }
lemmy_db_views_moderator = { path = "../db_views_moderator" }
lemmy_db_views_actor = { path = "../db_views_actor" }
lemmy_api_structs = { path = "../api_structs" }
lemmy_websocket = { path = "../websocket" }
diesel = "1.4.5"
bcrypt = "0.9.0"
chrono = { version = "0.4.19", features = ["serde"] }
serde_json = { version = "1.0.61", features = ["preserve_order"] }
serde = { version = "1.0.123", features = ["derive"] }
actix = "0.10.0"
actix-web = { version = "3.3.2", default-features = false }
actix-rt = { version = "1.1.1", default-features = false }
awc = { version = "2.0.3", default-features = false }
log = "0.4.14"
rand = "0.8.3"
strum = "0.20.0"
strum_macros = "0.20.1"
lazy_static = "1.4.0"
url = { version = "2.2.1", features = ["serde"] }
openssl = "0.10.32"
http = "0.2.3"
http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
base64 = "0.13.0"
tokio = "0.3.6"
futures = "0.3.12"
itertools = "0.10.0"
uuid = { version = "0.8.2", features = ["serde", "v4"] }
sha2 = "0.9.3"
async-trait = "0.1.42"
captcha = "0.0.8"
anyhow = "1.0.38"
thiserror = "1.0.23"
background-jobs = "0.8.0"
reqwest = { version = "0.10.10", features = ["json"] }

View file

@ -1,97 +0,0 @@
use crate::{get_local_user_view_from_jwt, Perform};
use actix_web::web::Data;
use lemmy_api_structs::websocket::*;
use lemmy_utils::{ConnectionId, LemmyError};
use lemmy_websocket::{
messages::{JoinCommunityRoom, JoinModRoom, JoinPostRoom, JoinUserRoom},
LemmyContext,
};
#[async_trait::async_trait(?Send)]
impl Perform for UserJoin {
type Response = UserJoinResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<UserJoinResponse, LemmyError> {
let data: &UserJoin = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
if let Some(ws_id) = websocket_id {
context.chat_server().do_send(JoinUserRoom {
local_user_id: local_user_view.local_user.id,
id: ws_id,
});
}
Ok(UserJoinResponse { joined: true })
}
}
#[async_trait::async_trait(?Send)]
impl Perform for CommunityJoin {
type Response = CommunityJoinResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<CommunityJoinResponse, LemmyError> {
let data: &CommunityJoin = &self;
if let Some(ws_id) = websocket_id {
context.chat_server().do_send(JoinCommunityRoom {
community_id: data.community_id,
id: ws_id,
});
}
Ok(CommunityJoinResponse { joined: true })
}
}
#[async_trait::async_trait(?Send)]
impl Perform for ModJoin {
type Response = ModJoinResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<ModJoinResponse, LemmyError> {
let data: &ModJoin = &self;
if let Some(ws_id) = websocket_id {
context.chat_server().do_send(JoinModRoom {
community_id: data.community_id,
id: ws_id,
});
}
Ok(ModJoinResponse { joined: true })
}
}
#[async_trait::async_trait(?Send)]
impl Perform for PostJoin {
type Response = PostJoinResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<PostJoinResponse, LemmyError> {
let data: &PostJoin = &self;
if let Some(ws_id) = websocket_id {
context.chat_server().do_send(JoinPostRoom {
post_id: data.post_id,
id: ws_id,
});
}
Ok(PostJoinResponse { joined: true })
}
}

View file

@ -1,24 +0,0 @@
[package]
name = "lemmy_api_structs"
version = "0.1.0"
edition = "2018"
[lib]
name = "lemmy_api_structs"
path = "src/lib.rs"
doctest = false
[dependencies]
lemmy_db_queries = { path = "../db_queries" }
lemmy_db_views = { path = "../db_views" }
lemmy_db_views_moderator = { path = "../db_views_moderator" }
lemmy_db_views_actor = { path = "../db_views_actor" }
lemmy_db_schema = { path = "../db_schema" }
lemmy_utils = { path = "../utils" }
serde = { version = "1.0.123", features = ["derive"] }
log = "0.4.14"
diesel = "1.4.5"
actix-web = "3.3.2"
chrono = { version = "0.4.19", features = ["serde"] }
serde_json = { version = "1.0.61", features = ["preserve_order"] }
url = "2.2.1"

View file

@ -1,191 +0,0 @@
pub mod comment;
pub mod community;
pub mod person;
pub mod post;
pub mod site;
pub mod websocket;
use diesel::PgConnection;
use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::{
source::{
comment::Comment,
person::Person,
person_mention::{PersonMention, PersonMentionForm},
post::Post,
},
LocalUserId,
};
use lemmy_db_views::local_user_view::LocalUserView;
use lemmy_utils::{email::send_email, settings::structs::Settings, utils::MentionData, LemmyError};
use log::error;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Serialize, Deserialize, Debug)]
pub struct WebFingerLink {
pub rel: Option<String>,
#[serde(rename(serialize = "type", deserialize = "type"))]
pub type_: Option<String>,
pub href: Option<Url>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct WebFingerResponse {
pub subject: String,
pub aliases: Vec<Url>,
pub links: Vec<WebFingerLink>,
}
pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
where
F: FnOnce(&diesel::PgConnection) -> T + Send + 'static,
T: Send + 'static,
{
let pool = pool.clone();
let res = actix_web::web::block(move || {
let conn = pool.get()?;
let res = (f)(&conn);
Ok(res) as Result<_, LemmyError>
})
.await?;
Ok(res)
}
pub async fn send_local_notifs(
mentions: Vec<MentionData>,
comment: Comment,
person: Person,
post: Post,
pool: &DbPool,
do_send_email: bool,
) -> Result<Vec<LocalUserId>, LemmyError> {
let ids = blocking(pool, move |conn| {
do_send_local_notifs(conn, &mentions, &comment, &person, &post, do_send_email)
})
.await?;
Ok(ids)
}
fn do_send_local_notifs(
conn: &PgConnection,
mentions: &[MentionData],
comment: &Comment,
person: &Person,
post: &Post,
do_send_email: bool,
) -> Vec<LocalUserId> {
let mut recipient_ids = Vec::new();
// Send the local mentions
for mention in mentions
.iter()
.filter(|m| m.is_local() && m.name.ne(&person.name))
.collect::<Vec<&MentionData>>()
{
if let Ok(mention_user_view) = LocalUserView::read_from_name(&conn, &mention.name) {
// TODO
// At some point, make it so you can't tag the parent creator either
// This can cause two notifications, one for reply and the other for mention
recipient_ids.push(mention_user_view.local_user.id);
let user_mention_form = PersonMentionForm {
recipient_id: mention_user_view.person.id,
comment_id: comment.id,
read: None,
};
// Allow this to fail softly, since comment edits might re-update or replace it
// Let the uniqueness handle this fail
PersonMention::create(&conn, &user_mention_form).ok();
// Send an email to those local users that have notifications on
if do_send_email {
send_email_to_user(
&mention_user_view,
"Mentioned by",
"Person Mention",
&comment.content,
)
}
}
}
// Send notifs to the parent commenter / poster
match comment.parent_id {
Some(parent_id) => {
if let Ok(parent_comment) = Comment::read(&conn, parent_id) {
// Don't send a notif to yourself
if parent_comment.creator_id != person.id {
// Get the parent commenter local_user
if let Ok(parent_user_view) = LocalUserView::read_person(&conn, parent_comment.creator_id)
{
recipient_ids.push(parent_user_view.local_user.id);
if do_send_email {
send_email_to_user(
&parent_user_view,
"Reply from",
"Comment Reply",
&comment.content,
)
}
}
}
}
}
// Its a post
None => {
if post.creator_id != person.id {
if let Ok(parent_user_view) = LocalUserView::read_person(&conn, post.creator_id) {
recipient_ids.push(parent_user_view.local_user.id);
if do_send_email {
send_email_to_user(
&parent_user_view,
"Reply from",
"Post Reply",
&comment.content,
)
}
}
}
}
};
recipient_ids
}
pub fn send_email_to_user(
local_user_view: &LocalUserView,
subject_text: &str,
body_text: &str,
comment_content: &str,
) {
if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email {
return;
}
if let Some(user_email) = &local_user_view.local_user.email {
let subject = &format!(
"{} - {} {}",
subject_text,
Settings::get().hostname(),
local_user_view.person.name,
);
let html = &format!(
"<h1>{}</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
body_text,
local_user_view.person.name,
comment_content,
Settings::get().get_protocol_and_hostname()
);
match send_email(subject, &user_email, &local_user_view.person.name, html) {
Ok(_o) => _o,
Err(e) => error!("{}", e),
};
}
}

View file

@ -1,42 +0,0 @@
use lemmy_db_schema::{CommunityId, PostId};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Debug)]
pub struct UserJoin {
pub auth: String,
}
#[derive(Serialize, Clone)]
pub struct UserJoinResponse {
pub joined: bool,
}
#[derive(Deserialize, Debug)]
pub struct CommunityJoin {
pub community_id: CommunityId,
}
#[derive(Serialize, Clone)]
pub struct CommunityJoinResponse {
pub joined: bool,
}
#[derive(Deserialize, Debug)]
pub struct ModJoin {
pub community_id: CommunityId,
}
#[derive(Serialize, Clone)]
pub struct ModJoinResponse {
pub joined: bool,
}
#[derive(Deserialize, Debug)]
pub struct PostJoin {
pub post_id: PostId,
}
#[derive(Serialize, Clone)]
pub struct PostJoinResponse {
pub joined: bool,
}

View file

@ -1,131 +0,0 @@
use crate::{
activities::send::generate_activity_id,
activity_queue::send_activity_single_dest,
extensions::context::lemmy_context,
objects::ToApub,
ActorType,
ApubObjectType,
};
use activitystreams::{
activity::{
kind::{CreateType, DeleteType, UndoType, UpdateType},
Create,
Delete,
Undo,
Update,
},
prelude::*,
};
use lemmy_api_structs::blocking;
use lemmy_db_queries::Crud;
use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage};
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
#[async_trait::async_trait(?Send)]
impl ApubObjectType for PrivateMessage {
/// Send out information about a newly created private message
async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?;
let recipient_id = self.recipient_id;
let recipient =
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
let mut create = Create::new(
creator.actor_id.to_owned().into_inner(),
note.into_any_base()?,
);
create
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(CreateType::Create)?)
.set_to(recipient.actor_id());
send_activity_single_dest(create, creator, recipient.inbox_url.into(), context).await?;
Ok(())
}
/// Send out information about an edited private message, to the followers of the community.
async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?;
let recipient_id = self.recipient_id;
let recipient =
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
let mut update = Update::new(
creator.actor_id.to_owned().into_inner(),
note.into_any_base()?,
);
update
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UpdateType::Update)?)
.set_to(recipient.actor_id());
send_activity_single_dest(update, creator, recipient.inbox_url.into(), context).await?;
Ok(())
}
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
let recipient_id = self.recipient_id;
let recipient =
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
let mut delete = Delete::new(
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
delete
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(recipient.actor_id());
send_activity_single_dest(delete, creator, recipient.inbox_url.into(), context).await?;
Ok(())
}
async fn send_undo_delete(
&self,
creator: &Person,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let recipient_id = self.recipient_id;
let recipient =
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
let mut delete = Delete::new(
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
delete
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(recipient.actor_id());
// Undo that fake activity
let mut undo = Undo::new(
creator.actor_id.to_owned().into_inner(),
delete.into_any_base()?,
);
undo
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?)
.set_to(recipient.actor_id());
send_activity_single_dest(undo, creator, recipient.inbox_url.into(), context).await?;
Ok(())
}
async fn send_remove(&self, _mod_: &Person, _context: &LemmyContext) -> Result<(), LemmyError> {
unimplemented!()
}
async fn send_undo_remove(
&self,
_mod_: &Person,
_context: &LemmyContext,
) -> Result<(), LemmyError> {
unimplemented!()
}
}

View file

@ -1,119 +0,0 @@
use crate::Crud;
use bcrypt::{hash, DEFAULT_COST};
use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{
naive_now,
schema::local_user::dsl::*,
source::local_user::{LocalUser, LocalUserForm},
LocalUserId,
PersonId,
};
mod safe_settings_type {
use crate::ToSafeSettings;
use lemmy_db_schema::{schema::local_user::columns::*, source::local_user::LocalUser};
type Columns = (
id,
person_id,
email,
admin,
show_nsfw,
theme,
default_sort_type,
default_listing_type,
lang,
show_avatars,
send_notifications_to_email,
matrix_user_id,
validator_time,
);
impl ToSafeSettings for LocalUser {
type SafeSettingsColumns = Columns;
/// Includes everything but the hashed password
fn safe_settings_columns_tuple() -> Self::SafeSettingsColumns {
(
id,
person_id,
email,
admin,
show_nsfw,
theme,
default_sort_type,
default_listing_type,
lang,
show_avatars,
send_notifications_to_email,
matrix_user_id,
validator_time,
)
}
}
}
pub trait LocalUser_ {
fn register(conn: &PgConnection, form: &LocalUserForm) -> Result<LocalUser, Error>;
fn update_password(
conn: &PgConnection,
local_user_id: LocalUserId,
new_password: &str,
) -> Result<LocalUser, Error>;
fn add_admin(conn: &PgConnection, person_id: PersonId, added: bool) -> Result<LocalUser, Error>;
}
impl LocalUser_ for LocalUser {
fn register(conn: &PgConnection, form: &LocalUserForm) -> Result<Self, Error> {
let mut edited_user = form.clone();
let password_hash =
hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
edited_user.password_encrypted = password_hash;
Self::create(&conn, &edited_user)
}
fn update_password(
conn: &PgConnection,
local_user_id: LocalUserId,
new_password: &str,
) -> Result<Self, Error> {
let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
diesel::update(local_user.find(local_user_id))
.set((
password_encrypted.eq(password_hash),
validator_time.eq(naive_now()),
))
.get_result::<Self>(conn)
}
fn add_admin(conn: &PgConnection, for_person_id: PersonId, added: bool) -> Result<Self, Error> {
diesel::update(local_user.filter(person_id.eq(for_person_id)))
.set(admin.eq(added))
.get_result::<Self>(conn)
}
}
impl Crud<LocalUserForm, LocalUserId> for LocalUser {
fn read(conn: &PgConnection, local_user_id: LocalUserId) -> Result<Self, Error> {
local_user.find(local_user_id).first::<Self>(conn)
}
fn delete(conn: &PgConnection, local_user_id: LocalUserId) -> Result<usize, Error> {
diesel::delete(local_user.find(local_user_id)).execute(conn)
}
fn create(conn: &PgConnection, form: &LocalUserForm) -> Result<Self, Error> {
insert_into(local_user)
.values(form)
.get_result::<Self>(conn)
}
fn update(
conn: &PgConnection,
local_user_id: LocalUserId,
form: &LocalUserForm,
) -> Result<Self, Error> {
diesel::update(local_user.find(local_user_id))
.set(form)
.get_result::<Self>(conn)
}
}

View file

@ -1,290 +0,0 @@
use crate::{ApubObject, Crud};
use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{
naive_now,
schema::person::dsl::*,
source::person::{Person, PersonForm},
DbUrl,
PersonId,
};
mod safe_type {
use crate::ToSafe;
use lemmy_db_schema::{schema::person::columns::*, source::person::Person};
type Columns = (
id,
name,
preferred_username,
avatar,
banned,
published,
updated,
actor_id,
bio,
local,
banner,
deleted,
inbox_url,
shared_inbox_url,
);
impl ToSafe for Person {
type SafeColumns = Columns;
fn safe_columns_tuple() -> Self::SafeColumns {
(
id,
name,
preferred_username,
avatar,
banned,
published,
updated,
actor_id,
bio,
local,
banner,
deleted,
inbox_url,
shared_inbox_url,
)
}
}
}
mod safe_type_alias_1 {
use crate::ToSafe;
use lemmy_db_schema::{schema::person_alias_1::columns::*, source::person::PersonAlias1};
type Columns = (
id,
name,
preferred_username,
avatar,
banned,
published,
updated,
actor_id,
bio,
local,
banner,
deleted,
inbox_url,
shared_inbox_url,
);
impl ToSafe for PersonAlias1 {
type SafeColumns = Columns;
fn safe_columns_tuple() -> Self::SafeColumns {
(
id,
name,
preferred_username,
avatar,
banned,
published,
updated,
actor_id,
bio,
local,
banner,
deleted,
inbox_url,
shared_inbox_url,
)
}
}
}
mod safe_type_alias_2 {
use crate::ToSafe;
use lemmy_db_schema::{schema::person_alias_2::columns::*, source::person::PersonAlias2};
type Columns = (
id,
name,
preferred_username,
avatar,
banned,
published,
updated,
actor_id,
bio,
local,
banner,
deleted,
inbox_url,
shared_inbox_url,
);
impl ToSafe for PersonAlias2 {
type SafeColumns = Columns;
fn safe_columns_tuple() -> Self::SafeColumns {
(
id,
name,
preferred_username,
avatar,
banned,
published,
updated,
actor_id,
bio,
local,
banner,
deleted,
inbox_url,
shared_inbox_url,
)
}
}
}
impl Crud<PersonForm, PersonId> for Person {
fn read(conn: &PgConnection, person_id: PersonId) -> Result<Self, Error> {
person
.filter(deleted.eq(false))
.find(person_id)
.first::<Self>(conn)
}
fn delete(conn: &PgConnection, person_id: PersonId) -> Result<usize, Error> {
diesel::delete(person.find(person_id)).execute(conn)
}
fn create(conn: &PgConnection, form: &PersonForm) -> Result<Self, Error> {
insert_into(person).values(form).get_result::<Self>(conn)
}
fn update(conn: &PgConnection, person_id: PersonId, form: &PersonForm) -> Result<Self, Error> {
diesel::update(person.find(person_id))
.set(form)
.get_result::<Self>(conn)
}
}
impl ApubObject<PersonForm> for Person {
fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error> {
use lemmy_db_schema::schema::person::dsl::*;
person
.filter(deleted.eq(false))
.filter(actor_id.eq(object_id))
.first::<Self>(conn)
}
fn upsert(conn: &PgConnection, person_form: &PersonForm) -> Result<Person, Error> {
insert_into(person)
.values(person_form)
.on_conflict(actor_id)
.do_update()
.set(person_form)
.get_result::<Self>(conn)
}
}
pub trait Person_ {
fn ban_person(conn: &PgConnection, person_id: PersonId, ban: bool) -> Result<Person, Error>;
fn find_by_name(conn: &PgConnection, name: &str) -> Result<Person, Error>;
fn mark_as_updated(conn: &PgConnection, person_id: PersonId) -> Result<Person, Error>;
fn delete_account(conn: &PgConnection, person_id: PersonId) -> Result<Person, Error>;
}
impl Person_ for Person {
fn ban_person(conn: &PgConnection, person_id: PersonId, ban: bool) -> Result<Self, Error> {
diesel::update(person.find(person_id))
.set(banned.eq(ban))
.get_result::<Self>(conn)
}
fn find_by_name(conn: &PgConnection, from_name: &str) -> Result<Person, Error> {
person
.filter(deleted.eq(false))
.filter(local.eq(true))
.filter(name.ilike(from_name))
.first::<Person>(conn)
}
fn mark_as_updated(conn: &PgConnection, person_id: PersonId) -> Result<Person, Error> {
diesel::update(person.find(person_id))
.set((last_refreshed_at.eq(naive_now()),))
.get_result::<Self>(conn)
}
fn delete_account(conn: &PgConnection, person_id: PersonId) -> Result<Person, Error> {
use lemmy_db_schema::schema::local_user;
// Set the local user info to none
diesel::update(local_user::table.filter(local_user::person_id.eq(person_id)))
.set((
local_user::email.eq::<Option<String>>(None),
local_user::matrix_user_id.eq::<Option<String>>(None),
))
.execute(conn)?;
diesel::update(person.find(person_id))
.set((
preferred_username.eq::<Option<String>>(None),
bio.eq::<Option<String>>(None),
deleted.eq(true),
updated.eq(naive_now()),
))
.get_result::<Self>(conn)
}
}
#[cfg(test)]
mod tests {
use crate::{establish_unpooled_connection, source::person::*};
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
let new_person = PersonForm {
name: "holly".into(),
preferred_username: None,
avatar: None,
banner: None,
banned: None,
deleted: None,
published: None,
updated: None,
actor_id: None,
bio: None,
local: None,
private_key: None,
public_key: None,
last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
};
let inserted_person = Person::create(&conn, &new_person).unwrap();
let expected_person = Person {
id: inserted_person.id,
name: "holly".into(),
preferred_username: None,
avatar: None,
banner: None,
banned: false,
deleted: false,
published: inserted_person.published,
updated: None,
actor_id: inserted_person.actor_id.to_owned(),
bio: None,
local: true,
private_key: None,
public_key: None,
last_refreshed_at: inserted_person.published,
inbox_url: inserted_person.inbox_url.to_owned(),
shared_inbox_url: None,
};
let read_person = Person::read(&conn, inserted_person.id).unwrap();
let updated_person = Person::update(&conn, inserted_person.id, &new_person).unwrap();
let num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
assert_eq!(expected_person, read_person);
assert_eq!(expected_person, inserted_person);
assert_eq!(expected_person, updated_person);
assert_eq!(1, num_deleted);
}
}

View file

@ -1,16 +0,0 @@
[package]
name = "lemmy_db_schema"
version = "0.1.0"
edition = "2018"
[lib]
doctest = false
[dependencies]
diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","serde_json"] }
chrono = { version = "0.4.19", features = ["serde"] }
serde = { version = "1.0.123", features = ["derive"] }
serde_json = { version = "1.0.61", features = ["preserve_order"] }
log = "0.4.14"
url = { version = "2.2.1", features = ["serde"] }
diesel-derive-newtype = "0.1"

View file

@ -1,115 +0,0 @@
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_derive_newtype;
use chrono::NaiveDateTime;
use diesel::{
backend::Backend,
deserialize::FromSql,
serialize::{Output, ToSql},
sql_types::Text,
};
use serde::{Deserialize, Serialize};
use std::{
fmt,
fmt::{Display, Formatter},
io::Write,
};
use url::Url;
pub mod schema;
pub mod source;
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct PostId(pub i32);
impl fmt::Display for PostId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct PersonId(pub i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct CommentId(pub i32);
impl fmt::Display for CommentId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct CommunityId(pub i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct LocalUserId(pub i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct PrivateMessageId(i32);
impl fmt::Display for PrivateMessageId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct PersonMentionId(i32);
#[repr(transparent)]
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, AsExpression, FromSqlRow)]
#[sql_type = "Text"]
pub struct DbUrl(Url);
impl<DB: Backend> ToSql<Text, DB> for DbUrl
where
String: ToSql<Text, DB>,
{
fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> diesel::serialize::Result {
self.0.to_string().to_sql(out)
}
}
impl<DB: Backend> FromSql<Text, DB> for DbUrl
where
String: FromSql<Text, DB>,
{
fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result<Self> {
let str = String::from_sql(bytes)?;
Ok(DbUrl(Url::parse(&str)?))
}
}
impl DbUrl {
pub fn into_inner(self) -> Url {
self.0
}
}
impl Display for DbUrl {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.to_owned().into_inner().fmt(f)
}
}
impl From<DbUrl> for Url {
fn from(url: DbUrl) -> Self {
url.0
}
}
impl From<Url> for DbUrl {
fn from(url: Url) -> Self {
DbUrl(url)
}
}
// TODO: can probably move this back to lemmy_db_queries
pub fn naive_now() -> NaiveDateTime {
chrono::prelude::Utc::now().naive_utc()
}

View file

@ -1,58 +0,0 @@
use crate::{schema::local_user, LocalUserId, PersonId};
use serde::Serialize;
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
#[table_name = "local_user"]
pub struct LocalUser {
pub id: LocalUserId,
pub person_id: PersonId,
pub password_encrypted: String,
pub email: Option<String>,
pub admin: bool,
pub show_nsfw: bool,
pub theme: String,
pub default_sort_type: i16,
pub default_listing_type: i16,
pub lang: String,
pub show_avatars: bool,
pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>,
pub validator_time: chrono::NaiveDateTime,
}
// TODO redo these, check table defaults
#[derive(Insertable, AsChangeset, Clone)]
#[table_name = "local_user"]
pub struct LocalUserForm {
pub person_id: PersonId,
pub password_encrypted: String,
pub email: Option<Option<String>>,
pub admin: Option<bool>,
pub show_nsfw: Option<bool>,
pub theme: Option<String>,
pub default_sort_type: Option<i16>,
pub default_listing_type: Option<i16>,
pub lang: Option<String>,
pub show_avatars: Option<bool>,
pub send_notifications_to_email: Option<bool>,
pub matrix_user_id: Option<Option<String>>,
}
/// A local user view that removes password encrypted
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
#[table_name = "local_user"]
pub struct LocalUserSettings {
pub id: LocalUserId,
pub person_id: PersonId,
pub email: Option<String>,
pub admin: bool,
pub show_nsfw: bool,
pub theme: String,
pub default_sort_type: i16,
pub default_listing_type: i16,
pub lang: String,
pub show_avatars: bool,
pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>,
pub validator_time: chrono::NaiveDateTime,
}

View file

@ -1,151 +0,0 @@
use crate::{
schema::{person, person_alias_1, person_alias_2},
DbUrl,
PersonId,
};
use serde::Serialize;
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
#[table_name = "person"]
pub struct Person {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub actor_id: DbUrl,
pub bio: Option<String>,
pub local: bool,
pub private_key: Option<String>,
pub public_key: Option<String>,
pub last_refreshed_at: chrono::NaiveDateTime,
pub banner: Option<DbUrl>,
pub deleted: bool,
pub inbox_url: DbUrl,
pub shared_inbox_url: Option<DbUrl>,
}
/// A safe representation of person, without the sensitive info
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
#[table_name = "person"]
pub struct PersonSafe {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub actor_id: DbUrl,
pub bio: Option<String>,
pub local: bool,
pub banner: Option<DbUrl>,
pub deleted: bool,
pub inbox_url: DbUrl,
pub shared_inbox_url: Option<DbUrl>,
}
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
#[table_name = "person_alias_1"]
pub struct PersonAlias1 {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub actor_id: DbUrl,
pub bio: Option<String>,
pub local: bool,
pub private_key: Option<String>,
pub public_key: Option<String>,
pub last_refreshed_at: chrono::NaiveDateTime,
pub banner: Option<DbUrl>,
pub deleted: bool,
pub inbox_url: DbUrl,
pub shared_inbox_url: Option<DbUrl>,
}
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
#[table_name = "person_alias_1"]
pub struct PersonSafeAlias1 {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub actor_id: DbUrl,
pub bio: Option<String>,
pub local: bool,
pub banner: Option<DbUrl>,
pub deleted: bool,
pub inbox_url: DbUrl,
pub shared_inbox_url: Option<DbUrl>,
}
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
#[table_name = "person_alias_2"]
pub struct PersonAlias2 {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub actor_id: DbUrl,
pub bio: Option<String>,
pub local: bool,
pub private_key: Option<String>,
pub public_key: Option<String>,
pub last_refreshed_at: chrono::NaiveDateTime,
pub banner: Option<DbUrl>,
pub deleted: bool,
pub inbox_url: DbUrl,
pub shared_inbox_url: Option<DbUrl>,
}
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
#[table_name = "person_alias_1"]
pub struct PersonSafeAlias2 {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub actor_id: DbUrl,
pub bio: Option<String>,
pub local: bool,
pub banner: Option<DbUrl>,
pub deleted: bool,
pub inbox_url: DbUrl,
pub shared_inbox_url: Option<DbUrl>,
}
#[derive(Insertable, AsChangeset, Clone)]
#[table_name = "person"]
pub struct PersonForm {
pub name: String,
pub preferred_username: Option<Option<String>>,
pub avatar: Option<Option<DbUrl>>,
pub banned: Option<bool>,
pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>,
pub actor_id: Option<DbUrl>,
pub bio: Option<Option<String>>,
pub local: Option<bool>,
pub private_key: Option<Option<String>>,
pub public_key: Option<Option<String>>,
pub last_refreshed_at: Option<chrono::NaiveDateTime>,
pub banner: Option<Option<DbUrl>>,
pub deleted: Option<bool>,
pub inbox_url: Option<DbUrl>,
pub shared_inbox_url: Option<Option<DbUrl>>,
}

View file

@ -1,27 +0,0 @@
use crate::{
schema::person_mention,
source::comment::Comment,
CommentId,
PersonId,
PersonMentionId,
};
use serde::Serialize;
#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
#[belongs_to(Comment)]
#[table_name = "person_mention"]
pub struct PersonMention {
pub id: PersonMentionId,
pub recipient_id: PersonId,
pub comment_id: CommentId,
pub read: bool,
pub published: chrono::NaiveDateTime,
}
#[derive(Insertable, AsChangeset)]
#[table_name = "person_mention"]
pub struct PersonMentionForm {
pub recipient_id: PersonId,
pub comment_id: CommentId,
pub read: Option<bool>,
}

View file

@ -1,18 +0,0 @@
[package]
name = "lemmy_db_views"
version = "0.1.0"
edition = "2018"
[lib]
doctest = false
[dependencies]
lemmy_db_queries = { path = "../db_queries" }
lemmy_db_schema = { path = "../db_schema" }
diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","serde_json"] }
serde = { version = "1.0.123", features = ["derive"] }
log = "0.4.14"
url = "2.2.1"
[dev-dependencies]
serial_test = "0.5.1"

View file

@ -1,147 +0,0 @@
use diesel::{result::Error, *};
use lemmy_db_queries::{aggregates::person_aggregates::PersonAggregates, ToSafe, ToSafeSettings};
use lemmy_db_schema::{
schema::{local_user, person, person_aggregates},
source::{
local_user::{LocalUser, LocalUserSettings},
person::{Person, PersonSafe},
},
LocalUserId,
PersonId,
};
use serde::Serialize;
#[derive(Debug, Serialize, Clone)]
pub struct LocalUserView {
pub local_user: LocalUser,
pub person: Person,
pub counts: PersonAggregates,
}
type LocalUserViewTuple = (LocalUser, Person, PersonAggregates);
impl LocalUserView {
pub fn read(conn: &PgConnection, local_user_id: LocalUserId) -> Result<Self, Error> {
let (local_user, person, counts) = local_user::table
.find(local_user_id)
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.select((
local_user::all_columns,
person::all_columns,
person_aggregates::all_columns,
))
.first::<LocalUserViewTuple>(conn)?;
Ok(Self {
local_user,
person,
counts,
})
}
pub fn read_person(conn: &PgConnection, person_id: PersonId) -> Result<Self, Error> {
let (local_user, person, counts) = local_user::table
.filter(person::id.eq(person_id))
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.select((
local_user::all_columns,
person::all_columns,
person_aggregates::all_columns,
))
.first::<LocalUserViewTuple>(conn)?;
Ok(Self {
local_user,
person,
counts,
})
}
// TODO check where this is used
pub fn read_from_name(conn: &PgConnection, name: &str) -> Result<Self, Error> {
let (local_user, person, counts) = local_user::table
.filter(person::name.eq(name))
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.select((
local_user::all_columns,
person::all_columns,
person_aggregates::all_columns,
))
.first::<LocalUserViewTuple>(conn)?;
Ok(Self {
person,
counts,
local_user,
})
}
pub fn find_by_email_or_name(conn: &PgConnection, name_or_email: &str) -> Result<Self, Error> {
let (local_user, person, counts) = local_user::table
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.filter(
person::name
.ilike(name_or_email)
.or(local_user::email.ilike(name_or_email)),
)
.select((
local_user::all_columns,
person::all_columns,
person_aggregates::all_columns,
))
.first::<LocalUserViewTuple>(conn)?;
Ok(Self {
person,
counts,
local_user,
})
}
pub fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<Self, Error> {
let (local_user, person, counts) = local_user::table
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.filter(local_user::email.eq(from_email))
.select((
local_user::all_columns,
person::all_columns,
person_aggregates::all_columns,
))
.first::<LocalUserViewTuple>(conn)?;
Ok(Self {
person,
counts,
local_user,
})
}
}
#[derive(Debug, Serialize, Clone)]
pub struct LocalUserSettingsView {
pub local_user: LocalUserSettings,
pub person: PersonSafe,
pub counts: PersonAggregates,
}
type LocalUserSettingsViewTuple = (LocalUserSettings, PersonSafe, PersonAggregates);
impl LocalUserSettingsView {
pub fn read(conn: &PgConnection, local_user_id: LocalUserId) -> Result<Self, Error> {
let (local_user, person, counts) = local_user::table
.find(local_user_id)
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.select((
LocalUser::safe_settings_columns_tuple(),
Person::safe_columns_tuple(),
person_aggregates::all_columns,
))
.first::<LocalUserSettingsViewTuple>(conn)?;
Ok(Self {
person,
counts,
local_user,
})
}
}

View file

@ -1,40 +0,0 @@
use diesel::{result::Error, *};
use lemmy_db_queries::ToSafe;
use lemmy_db_schema::{
schema::{community, community_person_ban, person},
source::{
community::{Community, CommunitySafe},
person::{Person, PersonSafe},
},
CommunityId,
PersonId,
};
use serde::Serialize;
#[derive(Debug, Serialize, Clone)]
pub struct CommunityPersonBanView {
pub community: CommunitySafe,
pub person: PersonSafe,
}
impl CommunityPersonBanView {
pub fn get(
conn: &PgConnection,
from_person_id: PersonId,
from_community_id: CommunityId,
) -> Result<Self, Error> {
let (community, person) = community_person_ban::table
.inner_join(community::table)
.inner_join(person::table)
.select((
Community::safe_columns_tuple(),
Person::safe_columns_tuple(),
))
.filter(community_person_ban::community_id.eq(from_community_id))
.filter(community_person_ban::person_id.eq(from_person_id))
.order_by(community_person_ban::published)
.first::<(CommunitySafe, PersonSafe)>(conn)?;
Ok(CommunityPersonBanView { community, person })
}
}

View file

@ -1,153 +0,0 @@
use diesel::{dsl::*, result::Error, *};
use lemmy_db_queries::{
aggregates::person_aggregates::PersonAggregates,
fuzzy_search,
limit_and_offset,
MaybeOptional,
SortType,
ToSafe,
ViewToVec,
};
use lemmy_db_schema::{
schema::{local_user, person, person_aggregates},
source::person::{Person, PersonSafe},
PersonId,
};
use serde::Serialize;
#[derive(Debug, Serialize, Clone)]
pub struct PersonViewSafe {
pub person: PersonSafe,
pub counts: PersonAggregates,
}
type PersonViewSafeTuple = (PersonSafe, PersonAggregates);
impl PersonViewSafe {
pub fn read(conn: &PgConnection, person_id: PersonId) -> Result<Self, Error> {
let (person, counts) = person::table
.find(person_id)
.inner_join(person_aggregates::table)
.select((Person::safe_columns_tuple(), person_aggregates::all_columns))
.first::<PersonViewSafeTuple>(conn)?;
Ok(Self { person, counts })
}
pub fn admins(conn: &PgConnection) -> Result<Vec<Self>, Error> {
let admins = person::table
.inner_join(person_aggregates::table)
.inner_join(local_user::table)
.select((Person::safe_columns_tuple(), person_aggregates::all_columns))
.filter(local_user::admin.eq(true))
.order_by(person::published)
.load::<PersonViewSafeTuple>(conn)?;
Ok(Self::from_tuple_to_vec(admins))
}
pub fn banned(conn: &PgConnection) -> Result<Vec<Self>, Error> {
let banned = person::table
.inner_join(person_aggregates::table)
.select((Person::safe_columns_tuple(), person_aggregates::all_columns))
.filter(person::banned.eq(true))
.load::<PersonViewSafeTuple>(conn)?;
Ok(Self::from_tuple_to_vec(banned))
}
}
pub struct PersonQueryBuilder<'a> {
conn: &'a PgConnection,
sort: &'a SortType,
search_term: Option<String>,
page: Option<i64>,
limit: Option<i64>,
}
impl<'a> PersonQueryBuilder<'a> {
pub fn create(conn: &'a PgConnection) -> Self {
PersonQueryBuilder {
conn,
search_term: None,
sort: &SortType::Hot,
page: None,
limit: None,
}
}
pub fn sort(mut self, sort: &'a SortType) -> Self {
self.sort = sort;
self
}
pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
self.search_term = search_term.get_optional();
self
}
pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
self.page = page.get_optional();
self
}
pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
self.limit = limit.get_optional();
self
}
pub fn list(self) -> Result<Vec<PersonViewSafe>, Error> {
let mut query = person::table
.inner_join(person_aggregates::table)
.select((Person::safe_columns_tuple(), person_aggregates::all_columns))
.into_boxed();
if let Some(search_term) = self.search_term {
query = query.filter(person::name.ilike(fuzzy_search(&search_term)));
}
query = match self.sort {
SortType::Hot => query
.order_by(person_aggregates::comment_score.desc())
.then_order_by(person::published.desc()),
SortType::Active => query
.order_by(person_aggregates::comment_score.desc())
.then_order_by(person::published.desc()),
SortType::New | SortType::MostComments | SortType::NewComments => {
query.order_by(person::published.desc())
}
SortType::TopAll => query.order_by(person_aggregates::comment_score.desc()),
SortType::TopYear => query
.filter(person::published.gt(now - 1.years()))
.order_by(person_aggregates::comment_score.desc()),
SortType::TopMonth => query
.filter(person::published.gt(now - 1.months()))
.order_by(person_aggregates::comment_score.desc()),
SortType::TopWeek => query
.filter(person::published.gt(now - 1.weeks()))
.order_by(person_aggregates::comment_score.desc()),
SortType::TopDay => query
.filter(person::published.gt(now - 1.days()))
.order_by(person_aggregates::comment_score.desc()),
};
let (limit, offset) = limit_and_offset(self.page, self.limit);
query = query.limit(limit).offset(offset);
let res = query.load::<PersonViewSafeTuple>(self.conn)?;
Ok(PersonViewSafe::from_tuple_to_vec(res))
}
}
impl ViewToVec for PersonViewSafe {
type DbTuple = PersonViewSafeTuple;
fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
items
.iter()
.map(|a| Self {
person: a.0.to_owned(),
counts: a.1.to_owned(),
})
.collect::<Vec<Self>>()
}
}

View file

@ -1,30 +0,0 @@
[package]
name = "lemmy_routes"
version = "0.1.0"
edition = "2018"
[lib]
doctest = false
[dependencies]
lemmy_utils = { path = "../utils" }
lemmy_websocket = { path = "../websocket" }
lemmy_db_queries = { path = "../db_queries" }
lemmy_db_views = { path = "../db_views" }
lemmy_db_views_actor = { path = "../db_views_actor" }
lemmy_db_schema = { path = "../db_schema" }
lemmy_api_structs = { path = "../api_structs" }
diesel = "1.4.5"
actix = "0.10.0"
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
actix-web-actors = { version = "3.0.0", default-features = false }
sha2 = "0.9.3"
log = "0.4.14"
anyhow = "1.0.38"
chrono = { version = "0.4.19", features = ["serde"] }
rss = "1.10.0"
serde = { version = "1.0.123", features = ["derive"] }
awc = { version = "2.0.3", default-features = false }
url = { version = "2.2.1", features = ["serde"] }
strum = "0.20.0"
lazy_static = "1.4.0"

View file

@ -1,69 +0,0 @@
use crate::settings::{CaptchaConfig, DatabaseConfig, FederationConfig, RateLimitConfig, Settings};
use std::net::{IpAddr, Ipv4Addr};
impl Default for Settings {
fn default() -> Self {
Self {
database: Some(DatabaseConfig::default()),
rate_limit: Some(RateLimitConfig::default()),
federation: Some(FederationConfig::default()),
captcha: Some(CaptchaConfig::default()),
email: None,
setup: None,
hostname: None,
bind: Some(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))),
port: Some(8536),
tls_enabled: Some(true),
jwt_secret: Some("changeme".into()),
pictrs_url: Some("http://pictrs:8080".into()),
iframely_url: Some("http://iframely".into()),
}
}
}
impl Default for DatabaseConfig {
fn default() -> Self {
Self {
user: "lemmy".into(),
password: "password".into(),
host: "localhost".into(),
port: 5432,
database: "lemmy".into(),
pool_size: 5,
}
}
}
impl Default for CaptchaConfig {
fn default() -> Self {
Self {
enabled: true,
difficulty: "medium".into(),
}
}
}
impl Default for FederationConfig {
fn default() -> Self {
Self {
enabled: false,
allowed_instances: None,
blocked_instances: None,
}
}
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
message: 180,
message_per_second: 60,
post: 6,
post_per_second: 600,
register: 3,
register_per_second: 3600,
image: 6,
image_per_second: 3600,
}
}
}

View file

@ -1,179 +0,0 @@
use crate::{
location_info,
settings::structs::{
CaptchaConfig,
DatabaseConfig,
EmailConfig,
FederationConfig,
RateLimitConfig,
Settings,
SetupConfig,
},
LemmyError,
};
use anyhow::{anyhow, Context};
use deser_hjson::from_str;
use log::warn;
use merge::Merge;
use std::{env, fs, io::Error, net::IpAddr, sync::RwLock};
pub(crate) mod defaults;
pub mod structs;
static CONFIG_FILE: &str = "config/config.hjson";
lazy_static! {
static ref SETTINGS: RwLock<Settings> = RwLock::new(match Settings::init() {
Ok(c) => c,
Err(e) => {
warn!(
"Couldn't load settings file, using default settings.\n{}",
e
);
Settings::default()
}
});
}
impl Settings {
/// Reads config from the files and environment.
/// First, defaults are loaded from CONFIG_FILE_DEFAULTS, then these values can be overwritten
/// from CONFIG_FILE (optional). Finally, values from the environment (with prefix LEMMY) are
/// added to the config.
///
/// Note: The env var `LEMMY_DATABASE_URL` is parsed in
/// `lemmy_db_queries/src/lib.rs::get_database_url_from_env()`
fn init() -> Result<Self, LemmyError> {
// Read the config file
let mut custom_config = from_str::<Settings>(&Self::read_config_file()?)?;
// Merge with env vars
custom_config.merge(envy::prefixed("LEMMY_").from_env::<Settings>()?);
// Merge with default
custom_config.merge(Settings::default());
if custom_config.hostname == Settings::default().hostname {
return Err(anyhow!("Hostname variable is not set!").into());
}
Ok(custom_config)
}
/// Returns the config as a struct.
pub fn get() -> Self {
SETTINGS.read().expect("read config").to_owned()
}
pub fn get_database_url(&self) -> String {
let conf = self.database();
format!(
"postgres://{}:{}@{}:{}/{}",
conf.user, conf.password, conf.host, conf.port, conf.database,
)
}
pub fn get_config_location() -> String {
env::var("LEMMY_CONFIG_LOCATION").unwrap_or_else(|_| CONFIG_FILE.to_string())
}
pub fn read_config_file() -> Result<String, Error> {
fs::read_to_string(Self::get_config_location())
}
pub fn get_allowed_instances(&self) -> Option<Vec<String>> {
self.federation().allowed_instances
}
pub fn get_blocked_instances(&self) -> Option<Vec<String>> {
self.federation().blocked_instances
}
/// Returns either "http" or "https", depending on tls_enabled setting
pub fn get_protocol_string(&self) -> &'static str {
if let Some(tls_enabled) = self.tls_enabled {
if tls_enabled {
"https"
} else {
"http"
}
} else {
"http"
}
}
/// Returns something like `http://localhost` or `https://lemmy.ml`,
/// with the correct protocol and hostname.
pub fn get_protocol_and_hostname(&self) -> String {
format!("{}://{}", self.get_protocol_string(), self.hostname())
}
/// When running the federation test setup in `api_tests/` or `docker/federation`, the `hostname`
/// variable will be like `lemmy-alpha:8541`. This method removes the port and returns
/// `lemmy-alpha` instead. It has no effect in production.
pub fn get_hostname_without_port(&self) -> Result<String, anyhow::Error> {
Ok(
self
.hostname()
.split(':')
.collect::<Vec<&str>>()
.first()
.context(location_info!())?
.to_string(),
)
}
pub fn save_config_file(data: &str) -> Result<String, LemmyError> {
fs::write(CONFIG_FILE, data)?;
// Reload the new settings
// From https://stackoverflow.com/questions/29654927/how-do-i-assign-a-string-to-a-mutable-static-variable/47181804#47181804
let mut new_settings = SETTINGS.write().expect("write config");
*new_settings = match Settings::init() {
Ok(c) => c,
Err(e) => panic!("{}", e),
};
Ok(Self::read_config_file()?)
}
pub fn database(&self) -> DatabaseConfig {
self.database.to_owned().unwrap_or_default()
}
pub fn hostname(&self) -> String {
self.hostname.to_owned().unwrap_or_default()
}
pub fn bind(&self) -> IpAddr {
self.bind.expect("return bind address")
}
pub fn port(&self) -> u16 {
self.port.unwrap_or_default()
}
pub fn tls_enabled(&self) -> bool {
self.tls_enabled.unwrap_or_default()
}
pub fn jwt_secret(&self) -> String {
self.jwt_secret.to_owned().unwrap_or_default()
}
pub fn pictrs_url(&self) -> String {
self.pictrs_url.to_owned().unwrap_or_default()
}
pub fn iframely_url(&self) -> String {
self.iframely_url.to_owned().unwrap_or_default()
}
pub fn rate_limit(&self) -> RateLimitConfig {
self.rate_limit.to_owned().unwrap_or_default()
}
pub fn federation(&self) -> FederationConfig {
self.federation.to_owned().unwrap_or_default()
}
pub fn captcha(&self) -> CaptchaConfig {
self.captcha.to_owned().unwrap_or_default()
}
pub fn email(&self) -> Option<EmailConfig> {
self.email.to_owned()
}
pub fn setup(&self) -> Option<SetupConfig> {
self.setup.to_owned()
}
}

View file

@ -1,72 +0,0 @@
use merge::Merge;
use serde::Deserialize;
use std::net::IpAddr;
#[derive(Debug, Deserialize, Clone, Merge)]
pub struct Settings {
pub(crate) database: Option<DatabaseConfig>,
pub(crate) rate_limit: Option<RateLimitConfig>,
pub(crate) federation: Option<FederationConfig>,
pub(crate) hostname: Option<String>,
pub(crate) bind: Option<IpAddr>,
pub(crate) port: Option<u16>,
pub(crate) tls_enabled: Option<bool>,
pub(crate) jwt_secret: Option<String>,
pub(crate) pictrs_url: Option<String>,
pub(crate) iframely_url: Option<String>,
pub(crate) captcha: Option<CaptchaConfig>,
pub(crate) email: Option<EmailConfig>,
pub(crate) setup: Option<SetupConfig>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct CaptchaConfig {
pub enabled: bool,
pub difficulty: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct DatabaseConfig {
pub user: String,
pub password: String,
pub host: String,
pub port: i32,
pub database: String,
pub pool_size: u32,
}
#[derive(Debug, Deserialize, Clone)]
pub struct EmailConfig {
pub smtp_server: String,
pub smtp_login: Option<String>,
pub smtp_password: Option<String>,
pub smtp_from_address: String,
pub use_tls: bool,
}
#[derive(Debug, Deserialize, Clone)]
pub struct FederationConfig {
pub enabled: bool,
pub allowed_instances: Option<Vec<String>>,
pub blocked_instances: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct RateLimitConfig {
pub message: i32,
pub message_per_second: i32,
pub post: i32,
pub post_per_second: i32,
pub register: i32,
pub register_per_second: i32,
pub image: i32,
pub image_per_second: i32,
}
#[derive(Debug, Deserialize, Clone)]
pub struct SetupConfig {
pub admin_username: String,
pub admin_password: String,
pub admin_email: Option<String>,
pub site_name: String,
}

View file

@ -1 +0,0 @@
pub const VERSION: &str = "0.10.0-rc.7";

View file

@ -1,30 +0,0 @@
[package]
name = "lemmy_websocket"
version = "0.1.0"
edition = "2018"
[lib]
name = "lemmy_websocket"
path = "src/lib.rs"
doctest = false
[dependencies]
lemmy_utils = { path = "../utils" }
lemmy_api_structs = { path = "../api_structs" }
lemmy_db_queries = { path = "../db_queries" }
lemmy_db_schema = { path = "../db_schema" }
reqwest = { version = "0.10.10", features = ["json"] }
log = "0.4.14"
rand = "0.8.3"
serde = { version = "1.0.123", features = ["derive"] }
serde_json = { version = "1.0.61", features = ["preserve_order"] }
actix = "0.10.0"
anyhow = "1.0.38"
diesel = "1.4.5"
background-jobs = "0.8.0"
tokio = "0.3.6"
strum = "0.20.0"
strum_macros = "0.20.1"
chrono = { version = "0.4.19", features = ["serde"] }
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
actix-web-actors = { version = "3.0.0", default-features = false }

View file

@ -1,9 +1,9 @@
ARG RUST_BUILDER_IMAGE=ekidd/rust-musl-builder:1.50.0 ARG RUST_BUILDER_IMAGE=ekidd/rust-musl-builder:1.47.0
# Cargo chef plan # Cargo chef plan
FROM $RUST_BUILDER_IMAGE as planner FROM $RUST_BUILDER_IMAGE as planner
WORKDIR /app WORKDIR /app
RUN cargo install cargo-chef RUN cargo install cargo-chef --version 0.1.6
# Copy dirs # Copy dirs
COPY ./ ./ COPY ./ ./
@ -15,7 +15,7 @@ RUN cargo chef prepare --recipe-path recipe.json
FROM $RUST_BUILDER_IMAGE as cacher FROM $RUST_BUILDER_IMAGE as cacher
ARG CARGO_BUILD_TARGET=x86_64-unknown-linux-musl ARG CARGO_BUILD_TARGET=x86_64-unknown-linux-musl
WORKDIR /app WORKDIR /app
RUN cargo install cargo-chef RUN cargo install cargo-chef --version 0.1.6
COPY --from=planner /app/recipe.json ./recipe.json COPY --from=planner /app/recipe.json ./recipe.json
RUN sudo chown -R rust:rust . RUN sudo chown -R rust:rust .
RUN cargo chef cook --target ${CARGO_BUILD_TARGET} --recipe-path recipe.json RUN cargo chef cook --target ${CARGO_BUILD_TARGET} --recipe-path recipe.json
@ -43,6 +43,15 @@ RUN strip ./target/$CARGO_BUILD_TARGET/$RUSTRELEASEDIR/lemmy_server
RUN cp ./target/$CARGO_BUILD_TARGET/$RUSTRELEASEDIR/lemmy_server /app/lemmy_server RUN cp ./target/$CARGO_BUILD_TARGET/$RUSTRELEASEDIR/lemmy_server /app/lemmy_server
# Build the docs
FROM $RUST_BUILDER_IMAGE as docs
WORKDIR /app
RUN cargo install mdbook --git https://github.com/Nutomic/mdBook.git \
--branch localization --rev 0982a82 --force
COPY --chown=rust:rust docs ./docs
RUN ls -la docs/
RUN mdbook build docs/
# The alpine runner # The alpine runner
FROM alpine:3.12 as lemmy FROM alpine:3.12 as lemmy
@ -56,7 +65,9 @@ RUN addgroup -g 1000 lemmy
RUN adduser -D -s /bin/sh -u 1000 -G lemmy lemmy RUN adduser -D -s /bin/sh -u 1000 -G lemmy lemmy
# Copy resources # Copy resources
COPY --chown=lemmy:lemmy config/defaults.hjson /config/defaults.hjson
COPY --chown=lemmy:lemmy --from=builder /app/lemmy_server /app/lemmy COPY --chown=lemmy:lemmy --from=builder /app/lemmy_server /app/lemmy
COPY --chown=lemmy:lemmy --from=docs /app/docs/book/ /app/documentation/
RUN chown lemmy:lemmy /app/lemmy RUN chown lemmy:lemmy /app/lemmy
USER lemmy USER lemmy

View file

@ -17,7 +17,7 @@ services:
- iframely - iframely
lemmy-ui: lemmy-ui:
image: dessalines/lemmy-ui:0.10.0-rc.7 image: dessalines/lemmy-ui:0.9.0-rc.4
ports: ports:
- "1235:1234" - "1235:1234"
restart: always restart: always
@ -42,7 +42,7 @@ services:
restart: always restart: always
pictrs: pictrs:
image: asonix/pictrs:v0.2.6-r1 image: asonix/pictrs:v0.2.5-r0
ports: ports:
- "8537:8080" - "8537:8080"
user: 991:991 user: 991:991
@ -57,4 +57,3 @@ services:
volumes: volumes:
- ../iframely.config.local.js:/iframely/config.local.js:ro - ../iframely.config.local.js:/iframely/config.local.js:ro
restart: always restart: always
mem_limit: 200m

View file

@ -1,7 +1,5 @@
# syntax=docker/dockerfile:experimental # syntax=docker/dockerfile:experimental
FROM rust:1.47-buster as rust
# Warning: this will not pick up migrations unless there are code changes
FROM rust:1.50-buster as rust
ENV HOME=/home/root ENV HOME=/home/root
@ -17,6 +15,13 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
RUN --mount=type=cache,target=/app/target \ RUN --mount=type=cache,target=/app/target \
cp target/debug/lemmy_server lemmy_server cp target/debug/lemmy_server lemmy_server
FROM rust:1.47-buster as docs
WORKDIR /app
RUN cargo install mdbook --git https://github.com/Nutomic/mdBook.git \
--branch localization --rev 0982a82 --force
COPY docs ./docs
RUN mdbook build docs/
FROM ubuntu:20.10 FROM ubuntu:20.10
# Install libpq for postgres and espeak # Install libpq for postgres and espeak
@ -26,6 +31,7 @@ RUN apt-get install -y libpq-dev espeak
# Copy resources # Copy resources
COPY config/defaults.hjson /config/defaults.hjson COPY config/defaults.hjson /config/defaults.hjson
COPY --from=rust /app/lemmy_server /app/lemmy COPY --from=rust /app/lemmy_server /app/lemmy
COPY --from=docs /app/docs/book/ /app/documentation/
EXPOSE 8536 EXPOSE 8536
CMD ["/app/lemmy"] CMD ["/app/lemmy"]

View file

@ -23,13 +23,13 @@ services:
pictrs: pictrs:
restart: always restart: always
image: asonix/pictrs:v0.2.6-r1 image: asonix/pictrs:v0.2.5-r0
user: 991:991 user: 991:991
volumes: volumes:
- ./volumes/pictrs_alpha:/mnt - ./volumes/pictrs_alpha:/mnt
lemmy-alpha-ui: lemmy-alpha-ui:
image: dessalines/lemmy-ui:0.10.0-rc.7 image: dessalines/lemmy-ui:0.9.0-rc.4
environment: environment:
- LEMMY_INTERNAL_HOST=lemmy-alpha:8541 - LEMMY_INTERNAL_HOST=lemmy-alpha:8541
- LEMMY_EXTERNAL_HOST=localhost:8541 - LEMMY_EXTERNAL_HOST=localhost:8541
@ -38,9 +38,20 @@ services:
- lemmy-alpha - lemmy-alpha
lemmy-alpha: lemmy-alpha:
image: lemmy-federation:latest image: lemmy-federation:latest
volumes:
- ./lemmy_alpha.hjson:/config/config.hjson
environment: environment:
- LEMMY_HOSTNAME=lemmy-alpha:8541
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_alpha:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_TLS_ENABLED=false
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon
- LEMMY_PORT=8541
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-alpha
- LEMMY_RATE_LIMIT__POST=99999
- LEMMY_RATE_LIMIT__REGISTER=99999
- LEMMY_CAPTCHA__ENABLED=false
- LEMMY_TEST_SEND_SYNC=1 - LEMMY_TEST_SEND_SYNC=1
- RUST_BACKTRACE=1 - RUST_BACKTRACE=1
- RUST_LOG=debug - RUST_LOG=debug
@ -58,7 +69,7 @@ services:
- ./volumes/postgres_alpha:/var/lib/postgresql/data - ./volumes/postgres_alpha:/var/lib/postgresql/data
lemmy-beta-ui: lemmy-beta-ui:
image: dessalines/lemmy-ui:0.10.0-rc.7 image: dessalines/lemmy-ui:0.9.0-rc.4
environment: environment:
- LEMMY_INTERNAL_HOST=lemmy-beta:8551 - LEMMY_INTERNAL_HOST=lemmy-beta:8551
- LEMMY_EXTERNAL_HOST=localhost:8551 - LEMMY_EXTERNAL_HOST=localhost:8551
@ -67,9 +78,20 @@ services:
- lemmy-beta - lemmy-beta
lemmy-beta: lemmy-beta:
image: lemmy-federation:latest image: lemmy-federation:latest
volumes:
- ./lemmy_beta.hjson:/config/config.hjson
environment: environment:
- LEMMY_HOSTNAME=lemmy-beta:8551
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_beta:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_TLS_ENABLED=false
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon
- LEMMY_PORT=8551
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-beta
- LEMMY_RATE_LIMIT__POST=99999
- LEMMY_RATE_LIMIT__REGISTER=99999
- LEMMY_CAPTCHA__ENABLED=false
- LEMMY_TEST_SEND_SYNC=1 - LEMMY_TEST_SEND_SYNC=1
- RUST_BACKTRACE=1 - RUST_BACKTRACE=1
- RUST_LOG=debug - RUST_LOG=debug
@ -87,7 +109,7 @@ services:
- ./volumes/postgres_beta:/var/lib/postgresql/data - ./volumes/postgres_beta:/var/lib/postgresql/data
lemmy-gamma-ui: lemmy-gamma-ui:
image: dessalines/lemmy-ui:0.10.0-rc.7 image: dessalines/lemmy-ui:0.9.0-rc.4
environment: environment:
- LEMMY_INTERNAL_HOST=lemmy-gamma:8561 - LEMMY_INTERNAL_HOST=lemmy-gamma:8561
- LEMMY_EXTERNAL_HOST=localhost:8561 - LEMMY_EXTERNAL_HOST=localhost:8561
@ -96,9 +118,20 @@ services:
- lemmy-gamma - lemmy-gamma
lemmy-gamma: lemmy-gamma:
image: lemmy-federation:latest image: lemmy-federation:latest
volumes:
- ./lemmy_gamma.hjson:/config/config.hjson
environment: environment:
- LEMMY_HOSTNAME=lemmy-gamma:8561
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_gamma:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_TLS_ENABLED=false
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon
- LEMMY_PORT=8561
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-gamma
- LEMMY_RATE_LIMIT__POST=99999
- LEMMY_RATE_LIMIT__REGISTER=99999
- LEMMY_CAPTCHA__ENABLED=false
- LEMMY_TEST_SEND_SYNC=1 - LEMMY_TEST_SEND_SYNC=1
- RUST_BACKTRACE=1 - RUST_BACKTRACE=1
- RUST_LOG=debug - RUST_LOG=debug
@ -117,7 +150,7 @@ services:
# An instance with only an allowlist for beta # An instance with only an allowlist for beta
lemmy-delta-ui: lemmy-delta-ui:
image: dessalines/lemmy-ui:0.10.0-rc.7 image: dessalines/lemmy-ui:0.9.0-rc.4
environment: environment:
- LEMMY_INTERNAL_HOST=lemmy-delta:8571 - LEMMY_INTERNAL_HOST=lemmy-delta:8571
- LEMMY_EXTERNAL_HOST=localhost:8571 - LEMMY_EXTERNAL_HOST=localhost:8571
@ -126,9 +159,20 @@ services:
- lemmy-delta - lemmy-delta
lemmy-delta: lemmy-delta:
image: lemmy-federation:latest image: lemmy-federation:latest
volumes:
- ./lemmy_delta.hjson:/config/config.hjson
environment: environment:
- LEMMY_HOSTNAME=lemmy-delta:8571
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_delta:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_TLS_ENABLED=false
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta
- LEMMY_PORT=8571
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-delta
- LEMMY_RATE_LIMIT__POST=99999
- LEMMY_RATE_LIMIT__REGISTER=99999
- LEMMY_CAPTCHA__ENABLED=false
- LEMMY_TEST_SEND_SYNC=1 - LEMMY_TEST_SEND_SYNC=1
- RUST_BACKTRACE=1 - RUST_BACKTRACE=1
- RUST_LOG=debug - RUST_LOG=debug
@ -147,7 +191,7 @@ services:
# An instance who has a blocklist, with lemmy-alpha blocked # An instance who has a blocklist, with lemmy-alpha blocked
lemmy-epsilon-ui: lemmy-epsilon-ui:
image: dessalines/lemmy-ui:0.10.0-rc.7 image: dessalines/lemmy-ui:0.9.0-rc.4
environment: environment:
- LEMMY_INTERNAL_HOST=lemmy-epsilon:8581 - LEMMY_INTERNAL_HOST=lemmy-epsilon:8581
- LEMMY_EXTERNAL_HOST=localhost:8581 - LEMMY_EXTERNAL_HOST=localhost:8581
@ -156,9 +200,20 @@ services:
- lemmy-epsilon - lemmy-epsilon
lemmy-epsilon: lemmy-epsilon:
image: lemmy-federation:latest image: lemmy-federation:latest
volumes:
- ./lemmy_epsilon.hjson:/config/config.hjson
environment: environment:
- LEMMY_HOSTNAME=lemmy-epsilon:8581
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_epsilon:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_TLS_ENABLED=false
- LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha
- LEMMY_PORT=8581
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-epsilon
- LEMMY_RATE_LIMIT__POST=99999
- LEMMY_RATE_LIMIT__REGISTER=99999
- LEMMY_CAPTCHA__ENABLED=false
- LEMMY_TEST_SEND_SYNC=1 - LEMMY_TEST_SEND_SYNC=1
- RUST_BACKTRACE=1 - RUST_BACKTRACE=1
- RUST_LOG=debug - RUST_LOG=debug

View file

@ -1,36 +0,0 @@
{
port: 8541
tls_enabled: false
jwt_secret: changeme
setup: {
admin_username: lemmy_alpha
admin_password: lemmy
site_name: lemmy-alpha
}
database: {
database: lemmy
user: lemmy
password: password
host: postgres_alpha
port: 5432
pool_size: 5
}
federation: {
enabled: true
allowed_instances: ["lemmy-beta","lemmy-gamma","lemmy-delta","lemmy-epsilon"]
}
captcha: {
enabled: false
difficulty: medium
}
rate_limit: {
message: 180
message_per_second: 60
post: 99999
post_per_second: 600
register: 99999
register_per_second: 3600
image: 6
image_per_second: 3600
}
}

View file

@ -1,37 +0,0 @@
{
hostname: lemmy-beta:8551
port: 8551
tls_enabled: false
jwt_secret: changeme
setup: {
admin_username: lemmy_beta
admin_password: lemmy
site_name: lemmy-beta
}
database: {
database: lemmy
user: lemmy
password: password
host: postgres_beta
port: 5432
pool_size: 5
}
federation: {
enabled: true
allowed_instances: ["lemmy-alpha","lemmy-gamma","lemmy-delta","lemmy-epsilon"]
}
captcha: {
enabled: false
difficulty: medium
}
rate_limit: {
message: 180
message_per_second: 60
post: 99999
post_per_second: 600
register: 99999
register_per_second: 3600
image: 6
image_per_second: 3600
}
}

View file

@ -1,37 +0,0 @@
{
hostname: lemmy-delta:8571
port: 8571
tls_enabled: false
jwt_secret: changeme
setup: {
admin_username: lemmy_delta
admin_password: lemmy
site_name: lemmy-delta
}
database: {
database: lemmy
user: lemmy
password: password
host: postgres_delta
port: 5432
pool_size: 5
}
federation: {
enabled: true
allowed_instances: ["lemmy-beta"]
}
captcha: {
enabled: false
difficulty: medium
}
rate_limit: {
message: 180
message_per_second: 60
post: 99999
post_per_second: 600
register: 99999
register_per_second: 3600
image: 6
image_per_second: 3600
}
}

View file

@ -1,37 +0,0 @@
{
hostname: lemmy-epsilon:8581
port: 8581
tls_enabled: false
jwt_secret: changeme
setup: {
admin_username: lemmy_epsilon
admin_password: lemmy
site_name: lemmy-epsilon
}
database: {
database: lemmy
user: lemmy
password: password
host: postgres_epsilon
port: 5432
pool_size: 5
}
federation: {
enabled: true
blocked_instances: ["lemmy-alpha"]
}
captcha: {
enabled: false
difficulty: medium
}
rate_limit: {
message: 180
message_per_second: 60
post: 99999
post_per_second: 600
register: 99999
register_per_second: 3600
image: 6
image_per_second: 3600
}
}

View file

@ -1,37 +0,0 @@
{
hostname: lemmy-gamma:8561
port: 8561
tls_enabled: false
jwt_secret: changeme
setup: {
admin_username: lemmy_gamma
admin_password: lemmy
site_name: lemmy-gamma
}
database: {
database: lemmy
user: lemmy
password: password
host: postgres_gamma
port: 5432
pool_size: 5
}
federation: {
enabled: true
allowed_instances: ["lemmy-alpha","lemmy-beta","lemmy-delta","lemmy-epsilon"]
}
captcha: {
enabled: false
difficulty: medium
}
rate_limit: {
message: 180
message_per_second: 60
post: 99999
post_per_second: 600
register: 99999
register_per_second: 3600
image: 6
image_per_second: 3600
}
}

View file

@ -17,7 +17,7 @@ http {
# Upload limit for pictshare # Upload limit for pictshare
client_max_body_size 50M; client_max_body_size 50M;
location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) { location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
proxy_pass http://lemmy-alpha; proxy_pass http://lemmy-alpha;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
@ -62,7 +62,7 @@ http {
# Upload limit for pictshare # Upload limit for pictshare
client_max_body_size 50M; client_max_body_size 50M;
location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) { location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
proxy_pass http://lemmy-beta; proxy_pass http://lemmy-beta;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
@ -107,7 +107,7 @@ http {
# Upload limit for pictshare # Upload limit for pictshare
client_max_body_size 50M; client_max_body_size 50M;
location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) { location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
proxy_pass http://lemmy-gamma; proxy_pass http://lemmy-gamma;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
@ -152,7 +152,7 @@ http {
# Upload limit for pictshare # Upload limit for pictshare
client_max_body_size 50M; client_max_body_size 50M;
location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) { location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
proxy_pass http://lemmy-delta; proxy_pass http://lemmy-delta;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
@ -197,7 +197,7 @@ http {
# Upload limit for pictshare # Upload limit for pictshare
client_max_body_size 50M; client_max_body_size 50M;
location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) { location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
proxy_pass http://lemmy-epsilon; proxy_pass http://lemmy-epsilon;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;

View file

@ -0,0 +1,31 @@
#!/bin/bash
set -e
# make sure there are no old containers or old data around
sudo docker-compose down
sudo rm -rf volumes
mkdir -p volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
sudo docker build ../../ --file ../dev/Dockerfile --tag lemmy-federation:latest
sudo mkdir -p volumes/pictrs_alpha
sudo chown -R 991:991 volumes/pictrs_alpha
sudo docker-compose up -d
pushd ../../api_tests
echo "Waiting for Lemmy to start..."
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8541/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8551/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8561/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8571/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8581/api/v1/site')" != "200" ]]; do sleep 1; done
yarn
yarn api-test || true
popd
sudo docker-compose down
sudo rm -r volumes

View file

@ -1,6 +1,6 @@
{ {
# for more info about the config, check out the documentation # for more info about the config, check out the documentation
# https://join.lemmy.ml/docs/en/administration/configuration.html # https://lemmy.ml/docs/administration_configuration.html
setup: { setup: {
# username for the admin user # username for the admin user
@ -29,10 +29,6 @@
password: "password" password: "password"
# host where postgres is running # host where postgres is running
host: "postgres" host: "postgres"
# port where postgres can be accessed
port: 5432
# maximum number of active sql connections
pool_size: 5
} }
# # optional: email sending configuration # # optional: email sending configuration
# email: { # email: {

View file

@ -1,9 +1,9 @@
ARG RUST_BUILDER_IMAGE=ekidd/rust-musl-builder:1.50.0 ARG RUST_BUILDER_IMAGE=ekidd/rust-musl-builder:1.47.0
# Cargo chef plan # Cargo chef plan
FROM $RUST_BUILDER_IMAGE as planner FROM $RUST_BUILDER_IMAGE as planner
WORKDIR /app WORKDIR /app
RUN cargo install cargo-chef RUN cargo install cargo-chef --version 0.1.6
# Copy dirs # Copy dirs
COPY ./ ./ COPY ./ ./
@ -15,7 +15,7 @@ RUN cargo chef prepare --recipe-path recipe.json
FROM $RUST_BUILDER_IMAGE as cacher FROM $RUST_BUILDER_IMAGE as cacher
ARG CARGO_BUILD_TARGET=x86_64-unknown-linux-musl ARG CARGO_BUILD_TARGET=x86_64-unknown-linux-musl
WORKDIR /app WORKDIR /app
RUN cargo install cargo-chef RUN cargo install cargo-chef --version 0.1.6
COPY --from=planner /app/recipe.json ./recipe.json COPY --from=planner /app/recipe.json ./recipe.json
RUN sudo chown -R rust:rust . RUN sudo chown -R rust:rust .
RUN cargo chef cook --release --target ${CARGO_BUILD_TARGET} --recipe-path recipe.json RUN cargo chef cook --release --target ${CARGO_BUILD_TARGET} --recipe-path recipe.json
@ -43,6 +43,14 @@ RUN strip ./target/$CARGO_BUILD_TARGET/$RUSTRELEASEDIR/lemmy_server
RUN cp ./target/$CARGO_BUILD_TARGET/$RUSTRELEASEDIR/lemmy_server /app/lemmy_server RUN cp ./target/$CARGO_BUILD_TARGET/$RUSTRELEASEDIR/lemmy_server /app/lemmy_server
# Build the docs
FROM $RUST_BUILDER_IMAGE as docs
WORKDIR /app
RUN cargo install mdbook --git https://github.com/Nutomic/mdBook.git \
--branch localization --rev 0982a82 --force
COPY --chown=rust:rust docs ./docs
RUN mdbook build docs/
# The alpine runner # The alpine runner
FROM alpine:3.12 as lemmy FROM alpine:3.12 as lemmy
@ -56,7 +64,9 @@ RUN addgroup -g 1000 lemmy
RUN adduser -D -s /bin/sh -u 1000 -G lemmy lemmy RUN adduser -D -s /bin/sh -u 1000 -G lemmy lemmy
# Copy resources # Copy resources
COPY --chown=lemmy:lemmy config/defaults.hjson /config/defaults.hjson
COPY --chown=lemmy:lemmy --from=builder /app/lemmy_server /app/lemmy COPY --chown=lemmy:lemmy --from=builder /app/lemmy_server /app/lemmy
COPY --chown=lemmy:lemmy --from=docs /app/docs/book/ /app/documentation/
RUN chown lemmy:lemmy /app/lemmy RUN chown lemmy:lemmy /app/lemmy
USER lemmy USER lemmy

View file

@ -1,39 +0,0 @@
ARG RUST_BUILDER_IMAGE=rust:1.50-slim-buster
# Build Lemmy
FROM $RUST_BUILDER_IMAGE as builder
# Install compilation dependencies
RUN apt-get update \
&& apt-get -y install --no-install-recommends libssl-dev pkg-config libpq-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY ./ ./
RUN cargo build --release
# reduce binary size
RUN strip ./target/release/lemmy_server
RUN cp ./target/release/lemmy_server /app/lemmy_server
# The Debian runner
FROM debian:buster-slim as lemmy
# Install libpq for postgres and espeak for captchas
RUN apt-get update \
&& apt-get -y install --no-install-recommends espeak postgresql-client libc6 libssl1.1 \
&& rm -rf /var/lib/apt/lists/*
RUN addgroup --gid 1000 lemmy
RUN adduser --no-create-home --shell /bin/sh --uid 1000 --gid 1000 lemmy
# Copy resources
COPY --chown=lemmy:lemmy --from=builder /app/lemmy_server /app/lemmy
RUN chown lemmy:lemmy /app/lemmy
USER lemmy
EXPOSE 8536
CMD ["/app/lemmy"]

View file

@ -9,8 +9,8 @@ new_tag="$1"
# Setting the version on the front end # Setting the version on the front end
cd ../../ cd ../../
# Setting the version on the backend # Setting the version on the backend
echo "pub const VERSION: &str = \"$new_tag\";" > "crates/utils/src/version.rs" echo "pub const VERSION: &str = \"$new_tag\";" > "lemmy_api/src/version.rs"
git add "crates/utils/src/version.rs" git add "lemmy_api/src/version.rs"
# Setting the version for Ansible # Setting the version for Ansible
echo $new_tag > "ansible/VERSION" echo $new_tag > "ansible/VERSION"
git add "ansible/VERSION" git add "ansible/VERSION"
@ -20,17 +20,21 @@ cd docker/prod || exit
# Changing various references to the Lemmy version # Changing various references to the Lemmy version
sed -i "s/dessalines\/lemmy-ui:.*/dessalines\/lemmy-ui:$new_tag/" ../dev/docker-compose.yml sed -i "s/dessalines\/lemmy-ui:.*/dessalines\/lemmy-ui:$new_tag/" ../dev/docker-compose.yml
sed -i "s/dessalines\/lemmy-ui:.*/dessalines\/lemmy-ui:$new_tag/" ../federation/docker-compose.yml sed -i "s/dessalines\/lemmy-ui:.*/dessalines\/lemmy-ui:$new_tag/" ../federation/docker-compose.yml
sed -i "s/dessalines\/lemmy-ui:.*/dessalines\/lemmy-ui:$new_tag/" ../prod/docker-compose.yml
sed -i "s/dessalines\/lemmy:.*/dessalines\/lemmy:$new_tag/" ../prod/docker-compose.yml sed -i "s/dessalines\/lemmy:.*/dessalines\/lemmy:$new_tag/" ../prod/docker-compose.yml
sed -i "s/dessalines\/lemmy-ui:.*/dessalines\/lemmy-ui:$new_tag/" ../prod/docker-compose.yml
sed -i "s/dessalines\/lemmy:v.*/dessalines\/lemmy:$new_tag/" ../travis/docker_push.sh
git add ../dev/docker-compose.yml git add ../dev/docker-compose.yml
git add ../prod/docker-compose.yml
git add ../federation/docker-compose.yml git add ../federation/docker-compose.yml
git add ../prod/docker-compose.yml
git add ../travis/docker_push.sh
# The commit # The commit
git commit -m"Version $new_tag" git commit -m"Version $new_tag"
git tag $new_tag git tag $new_tag
# Now doing the building on travis, but leave this in for when you need to do an arm build
# export COMPOSE_DOCKER_CLI_BUILD=1 # export COMPOSE_DOCKER_CLI_BUILD=1
# export DOCKER_BUILDKIT=1 # export DOCKER_BUILDKIT=1

View file

@ -12,7 +12,7 @@ services:
restart: always restart: always
lemmy: lemmy:
image: dessalines/lemmy:0.10.0-rc.7 image: dessalines/lemmy:0.9.0-rc.4
ports: ports:
- "127.0.0.1:8536:8536" - "127.0.0.1:8536:8536"
restart: always restart: always
@ -26,9 +26,9 @@ services:
- iframely - iframely
lemmy-ui: lemmy-ui:
image: dessalines/lemmy-ui:0.10.0-rc.7 image: dessalines/lemmy-ui:0.9.0-rc.4
ports: ports:
- "127.0.0.1:1235:1234" - "1235:1234"
restart: always restart: always
environment: environment:
- LEMMY_INTERNAL_HOST=lemmy:8536 - LEMMY_INTERNAL_HOST=lemmy:8536
@ -38,7 +38,7 @@ services:
- lemmy - lemmy
pictrs: pictrs:
image: asonix/pictrs:v0.2.6-r1 image: asonix/pictrs:v0.2.5-r0
ports: ports:
- "127.0.0.1:8537:8080" - "127.0.0.1:8537:8080"
user: 991:991 user: 991:991
@ -53,4 +53,4 @@ services:
volumes: volumes:
- ./iframely.config.local.js:/iframely/config.local.js:ro - ./iframely.config.local.js:/iframely/config.local.js:ro
restart: always restart: always
mem_limit: 200m mem_limit: 100m

View file

@ -0,0 +1,159 @@
version: '3.3'
services:
lemmy-alpha:
image: dessalines/lemmy:travis
environment:
- LEMMY_HOSTNAME=lemmy-alpha:8541
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_alpha:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_TLS_ENABLED=false
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon
- LEMMY_PORT=8541
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-alpha
- LEMMY_RATE_LIMIT__POST=99999
- LEMMY_RATE_LIMIT__REGISTER=99999
- LEMMY_CAPTCHA__ENABLED=false
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- postgres_alpha
ports:
- "8541:8541"
postgres_alpha:
image: postgres:12-alpine
environment:
- POSTGRES_USER=lemmy
- POSTGRES_PASSWORD=password
- POSTGRES_DB=lemmy
volumes:
- ./volumes/postgres_alpha:/var/lib/postgresql/data
lemmy-beta:
image: dessalines/lemmy:travis
environment:
- LEMMY_HOSTNAME=lemmy-beta:8551
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_beta:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_TLS_ENABLED=false
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon
- LEMMY_PORT=8551
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-beta
- LEMMY_RATE_LIMIT__POST=99999
- LEMMY_RATE_LIMIT__REGISTER=99999
- LEMMY_CAPTCHA__ENABLED=false
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- postgres_beta
ports:
- "8551:8551"
postgres_beta:
image: postgres:12-alpine
environment:
- POSTGRES_USER=lemmy
- POSTGRES_PASSWORD=password
- POSTGRES_DB=lemmy
volumes:
- ./volumes/postgres_beta:/var/lib/postgresql/data
lemmy-gamma:
image: dessalines/lemmy:travis
environment:
- LEMMY_HOSTNAME=lemmy-gamma:8561
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_gamma:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_TLS_ENABLED=false
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon
- LEMMY_PORT=8561
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-gamma
- LEMMY_RATE_LIMIT__POST=99999
- LEMMY_RATE_LIMIT__REGISTER=99999
- LEMMY_CAPTCHA__ENABLED=false
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- postgres_gamma
ports:
- "8561:8561"
postgres_gamma:
image: postgres:12-alpine
environment:
- POSTGRES_USER=lemmy
- POSTGRES_PASSWORD=password
- POSTGRES_DB=lemmy
volumes:
- ./volumes/postgres_gamma:/var/lib/postgresql/data
# An instance with only an allowlist for beta
lemmy-delta:
image: dessalines/lemmy:travis
environment:
- LEMMY_HOSTNAME=lemmy-delta:8571
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_delta:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_TLS_ENABLED=false
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta
- LEMMY_PORT=8571
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-delta
- LEMMY_RATE_LIMIT__POST=99999
- LEMMY_RATE_LIMIT__REGISTER=99999
- LEMMY_CAPTCHA__ENABLED=false
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- postgres_delta
ports:
- "8571:8571"
postgres_delta:
image: postgres:12-alpine
environment:
- POSTGRES_USER=lemmy
- POSTGRES_PASSWORD=password
- POSTGRES_DB=lemmy
volumes:
- ./volumes/postgres_delta:/var/lib/postgresql/data
# An instance who has a blocklist, with lemmy-alpha blocked
lemmy-epsilon:
image: dessalines/lemmy:travis
environment:
- LEMMY_HOSTNAME=lemmy-epsilon:8581
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_epsilon:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FEDERATION__ENABLED=true
- LEMMY_TLS_ENABLED=false
- LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha
- LEMMY_PORT=8581
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-epsilon
- LEMMY_RATE_LIMIT__POST=99999
- LEMMY_RATE_LIMIT__REGISTER=99999
- LEMMY_CAPTCHA__ENABLED=false
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- postgres_epsilon
ports:
- "8581:8581"
postgres_epsilon:
image: postgres:12-alpine
environment:
- POSTGRES_USER=lemmy
- POSTGRES_PASSWORD=password
- POSTGRES_DB=lemmy
volumes:
- ./volumes/postgres_epsilon:/var/lib/postgresql/data

View file

@ -0,0 +1,5 @@
#!/bin/sh
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
docker tag dessalines/lemmy:travis \
dessalines/lemmy:0.9.0-rc.4
docker push dessalines/lemmy:0.9.0-rc.4

28
docker/travis/run-tests.bash Executable file
View file

@ -0,0 +1,28 @@
#!/bin/bash
set -e
# make sure there are no old containers or old data around
sudo docker-compose down
sudo rm -rf volumes
mkdir -p volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
sudo docker build ../../ --file ../prod/Dockerfile --tag dessalines/lemmy:travis
sudo docker-compose up -d
pushd ../../api_tests
echo "Waiting for Lemmy to start..."
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8541/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8551/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8561/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8571/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8581/api/v1/site')" != "200" ]]; do sleep 1; done
yarn
yarn api-test
popd
sudo docker-compose down
sudo rm -r volumes/

1
docs Submodule

@ -0,0 +1 @@
Subproject commit cf3236bb620048897048027d8cdff34401ad85ee

51
lemmy_api/Cargo.toml Normal file
View file

@ -0,0 +1,51 @@
[package]
name = "lemmy_api"
version = "0.1.0"
authors = ["Felix Ableitner <me@nutomic.com>"]
edition = "2018"
[lib]
name = "lemmy_api"
path = "src/lib.rs"
[dependencies]
lemmy_apub = { path = "../lemmy_apub" }
lemmy_utils = { path = "../lemmy_utils" }
lemmy_db_queries = { path = "../lemmy_db_queries" }
lemmy_db_schema = { path = "../lemmy_db_schema" }
lemmy_db_views = { path = "../lemmy_db_views" }
lemmy_db_views_moderator = { path = "../lemmy_db_views_moderator" }
lemmy_db_views_actor = { path = "../lemmy_db_views_actor" }
lemmy_structs = { path = "../lemmy_structs" }
lemmy_websocket = { path = "../lemmy_websocket" }
diesel = "1.4.5"
bcrypt = "0.9.0"
chrono = { version = "0.4.19", features = ["serde"] }
serde_json = { version = "1.0.60", features = ["preserve_order"] }
serde = { version = "1.0.118", features = ["derive"] }
actix = "0.10.0"
actix-web = { version = "3.3.2", default-features = false }
actix-rt = { version = "1.1.1", default-features = false }
awc = { version = "2.0.3", default-features = false }
log = "0.4.11"
rand = "0.8.0"
strum = "0.20.0"
strum_macros = "0.20.1"
jsonwebtoken = "7.2.0"
lazy_static = "1.4.0"
url = { version = "2.2.0", features = ["serde"] }
openssl = "0.10.31"
http = "0.2.2"
http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
base64 = "0.13.0"
tokio = "0.3.6"
futures = "0.3.8"
itertools = "0.9.0"
uuid = { version = "0.8.1", features = ["serde", "v4"] }
sha2 = "0.9.2"
async-trait = "0.1.42"
captcha = "0.0.8"
anyhow = "1.0.36"
thiserror = "1.0.22"
background-jobs = "0.8.0"
reqwest = { version = "0.10.10", features = ["json"] }

View file

@ -1,17 +1,14 @@
use crate::settings::structs::Settings;
use chrono::Utc;
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
use lemmy_db_schema::source::user::User_;
use lemmy_utils::settings::Settings;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
type Jwt = String; type Jwt = String;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Claims { pub struct Claims {
/// local_user_id, standard claim by RFC 7519. pub id: i32,
pub sub: i32,
pub iss: String, pub iss: String,
/// Time when this token was issued as UNIX-timestamp in seconds
pub iat: i64,
} }
impl Claims { impl Claims {
@ -22,21 +19,20 @@ impl Claims {
}; };
decode::<Claims>( decode::<Claims>(
&jwt, &jwt,
&DecodingKey::from_secret(Settings::get().jwt_secret().as_ref()), &DecodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
&v, &v,
) )
} }
pub fn jwt(local_user_id: i32) -> Result<Jwt, jsonwebtoken::errors::Error> { pub fn jwt(user: User_, hostname: String) -> Result<Jwt, jsonwebtoken::errors::Error> {
let my_claims = Claims { let my_claims = Claims {
sub: local_user_id, id: user.id,
iss: Settings::get().hostname(), iss: hostname,
iat: Utc::now().timestamp(),
}; };
encode( encode(
&Header::default(), &Header::default(),
&my_claims, &my_claims,
&EncodingKey::from_secret(Settings::get().jwt_secret().as_ref()), &EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
) )
} }
} }

View file

@ -2,15 +2,14 @@ use crate::{
check_community_ban, check_community_ban,
check_downvotes_enabled, check_downvotes_enabled,
collect_moderated_communities, collect_moderated_communities,
get_local_user_view_from_jwt,
get_local_user_view_from_jwt_opt,
get_post, get_post,
get_user_from_jwt,
get_user_from_jwt_opt,
is_mod_or_admin, is_mod_or_admin,
Perform, Perform,
}; };
use actix_web::web::Data; use actix_web::web::Data;
use lemmy_api_structs::{blocking, comment::*, send_local_notifs}; use lemmy_apub::{ApubLikeableType, ApubObjectType};
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
use lemmy_db_queries::{ use lemmy_db_queries::{
source::comment::Comment_, source::comment::Comment_,
Crud, Crud,
@ -20,18 +19,16 @@ use lemmy_db_queries::{
Saveable, Saveable,
SortType, SortType,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::source::{comment::*, comment_report::*, moderator::*};
source::{comment::*, comment_report::*, moderator::*},
LocalUserId,
};
use lemmy_db_views::{ use lemmy_db_views::{
comment_report_view::{CommentReportQueryBuilder, CommentReportView}, comment_report_view::{CommentReportQueryBuilder, CommentReportView},
comment_view::{CommentQueryBuilder, CommentView}, comment_view::{CommentQueryBuilder, CommentView},
local_user_view::LocalUserView,
}; };
use lemmy_structs::{blocking, comment::*, send_local_notifs};
use lemmy_utils::{ use lemmy_utils::{
apub::{make_apub_endpoint, EndpointType},
utils::{remove_slurs, scrape_text_for_mentions}, utils::{remove_slurs, scrape_text_for_mentions},
ApiError, APIError,
ConnectionId, ConnectionId,
LemmyError, LemmyError,
}; };
@ -52,7 +49,7 @@ impl Perform for CreateComment {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> { ) -> Result<CommentResponse, LemmyError> {
let data: &CreateComment = &self; let data: &CreateComment = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let content_slurs_removed = remove_slurs(&data.content.to_owned()); let content_slurs_removed = remove_slurs(&data.content.to_owned());
@ -60,31 +57,18 @@ impl Perform for CreateComment {
let post_id = data.post_id; let post_id = data.post_id;
let post = get_post(post_id, context.pool()).await?; let post = get_post(post_id, context.pool()).await?;
check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?; check_community_ban(user.id, post.community_id, context.pool()).await?;
// Check if post is locked, no new comments // Check if post is locked, no new comments
if post.locked { if post.locked {
return Err(ApiError::err("locked").into()); return Err(APIError::err("locked").into());
}
// If there's a parent_id, check to make sure that comment is in that post
if let Some(parent_id) = data.parent_id {
// Make sure the parent comment exists
let parent =
match blocking(context.pool(), move |conn| Comment::read(&conn, parent_id)).await? {
Ok(comment) => comment,
Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()),
};
if parent.post_id != post_id {
return Err(ApiError::err("couldnt_create_comment").into());
}
} }
let comment_form = CommentForm { let comment_form = CommentForm {
content: content_slurs_removed, content: content_slurs_removed,
parent_id: data.parent_id.to_owned(), parent_id: data.parent_id.to_owned(),
post_id: data.post_id, post_id: data.post_id,
creator_id: local_user_view.person.id, creator_id: user.id,
removed: None, removed: None,
deleted: None, deleted: None,
read: None, read: None,
@ -102,26 +86,23 @@ impl Perform for CreateComment {
.await? .await?
{ {
Ok(comment) => comment, Ok(comment) => comment,
Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()), Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
}; };
// Necessary to update the ap_id // Necessary to update the ap_id
let inserted_comment_id = inserted_comment.id; let inserted_comment_id = inserted_comment.id;
let updated_comment: Comment = let updated_comment: Comment = match blocking(context.pool(), move |conn| {
match blocking(context.pool(), move |conn| -> Result<Comment, LemmyError> {
let apub_id = let apub_id =
generate_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string())?; make_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string()).to_string();
Ok(Comment::update_ap_id(&conn, inserted_comment_id, apub_id)?) Comment::update_ap_id(&conn, inserted_comment_id, apub_id)
}) })
.await? .await?
{ {
Ok(comment) => comment, Ok(comment) => comment,
Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()), Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
}; };
updated_comment updated_comment.send_create(&user, context).await?;
.send_create(&local_user_view.person, context)
.await?;
// Scan the comment for user mentions, add those rows // Scan the comment for user mentions, add those rows
let post_id = post.id; let post_id = post.id;
@ -129,7 +110,7 @@ impl Perform for CreateComment {
let recipient_ids = send_local_notifs( let recipient_ids = send_local_notifs(
mentions, mentions,
updated_comment.clone(), updated_comment.clone(),
local_user_view.person.clone(), &user,
post, post,
context.pool(), context.pool(),
true, true,
@ -140,35 +121,33 @@ impl Perform for CreateComment {
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
post_id, post_id,
person_id: local_user_view.person.id, user_id: user.id,
score: 1, score: 1,
}; };
let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form); let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
if blocking(context.pool(), like).await?.is_err() { if blocking(context.pool(), like).await?.is_err() {
return Err(ApiError::err("couldnt_like_comment").into()); return Err(APIError::err("couldnt_like_comment").into());
} }
updated_comment updated_comment.send_like(&user, context).await?;
.send_like(&local_user_view.person, context)
.await?;
let person_id = local_user_view.person.id; let user_id = user.id;
let mut comment_view = blocking(context.pool(), move |conn| { let mut comment_view = blocking(context.pool(), move |conn| {
CommentView::read(&conn, inserted_comment.id, Some(person_id)) CommentView::read(&conn, inserted_comment.id, Some(user_id))
}) })
.await??; .await??;
// If its a comment to yourself, mark it as read // If its a comment to yourself, mark it as read
let comment_id = comment_view.comment.id; let comment_id = comment_view.comment.id;
if local_user_view.person.id == comment_view.get_recipient_id() { if user.id == comment_view.get_recipient_id() {
match blocking(context.pool(), move |conn| { match blocking(context.pool(), move |conn| {
Comment::update_read(conn, comment_id, true) Comment::update_read(conn, comment_id, true)
}) })
.await? .await?
{ {
Ok(comment) => comment, Ok(comment) => comment,
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()), Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
}; };
comment_view.comment.read = true; comment_view.comment.read = true;
} }
@ -201,42 +180,35 @@ impl Perform for EditComment {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> { ) -> Result<CommentResponse, LemmyError> {
let data: &EditComment = &self; let data: &EditComment = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let comment_id = data.comment_id; let edit_id = data.edit_id;
let orig_comment = blocking(context.pool(), move |conn| { let orig_comment = blocking(context.pool(), move |conn| {
CommentView::read(&conn, comment_id, None) CommentView::read(&conn, edit_id, None)
}) })
.await??; .await??;
check_community_ban( check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
local_user_view.person.id,
orig_comment.community.id,
context.pool(),
)
.await?;
// Verify that only the creator can edit // Verify that only the creator can edit
if local_user_view.person.id != orig_comment.creator.id { if user.id != orig_comment.creator.id {
return Err(ApiError::err("no_comment_edit_allowed").into()); return Err(APIError::err("no_comment_edit_allowed").into());
} }
// Do the update // Do the update
let content_slurs_removed = remove_slurs(&data.content.to_owned()); let content_slurs_removed = remove_slurs(&data.content.to_owned());
let comment_id = data.comment_id; let edit_id = data.edit_id;
let updated_comment = match blocking(context.pool(), move |conn| { let updated_comment = match blocking(context.pool(), move |conn| {
Comment::update_content(conn, comment_id, &content_slurs_removed) Comment::update_content(conn, edit_id, &content_slurs_removed)
}) })
.await? .await?
{ {
Ok(comment) => comment, Ok(comment) => comment,
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()), Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
}; };
// Send the apub update // Send the apub update
updated_comment updated_comment.send_update(&user, context).await?;
.send_update(&local_user_view.person, context)
.await?;
// Do the mentions / recipients // Do the mentions / recipients
let updated_comment_content = updated_comment.content.to_owned(); let updated_comment_content = updated_comment.content.to_owned();
@ -244,17 +216,17 @@ impl Perform for EditComment {
let recipient_ids = send_local_notifs( let recipient_ids = send_local_notifs(
mentions, mentions,
updated_comment, updated_comment,
local_user_view.person.clone(), &user,
orig_comment.post, orig_comment.post,
context.pool(), context.pool(),
false, false,
) )
.await?; .await?;
let comment_id = data.comment_id; let edit_id = data.edit_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let comment_view = blocking(context.pool(), move |conn| { let comment_view = blocking(context.pool(), move |conn| {
CommentView::read(conn, comment_id, Some(person_id)) CommentView::read(conn, edit_id, Some(user_id))
}) })
.await??; .await??;
@ -284,53 +256,44 @@ impl Perform for DeleteComment {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> { ) -> Result<CommentResponse, LemmyError> {
let data: &DeleteComment = &self; let data: &DeleteComment = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let comment_id = data.comment_id; let edit_id = data.edit_id;
let orig_comment = blocking(context.pool(), move |conn| { let orig_comment = blocking(context.pool(), move |conn| {
CommentView::read(&conn, comment_id, None) CommentView::read(&conn, edit_id, None)
}) })
.await??; .await??;
check_community_ban( check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
local_user_view.person.id,
orig_comment.community.id,
context.pool(),
)
.await?;
// Verify that only the creator can delete // Verify that only the creator can delete
if local_user_view.person.id != orig_comment.creator.id { if user.id != orig_comment.creator.id {
return Err(ApiError::err("no_comment_edit_allowed").into()); return Err(APIError::err("no_comment_edit_allowed").into());
} }
// Do the delete // Do the delete
let deleted = data.deleted; let deleted = data.deleted;
let updated_comment = match blocking(context.pool(), move |conn| { let updated_comment = match blocking(context.pool(), move |conn| {
Comment::update_deleted(conn, comment_id, deleted) Comment::update_deleted(conn, edit_id, deleted)
}) })
.await? .await?
{ {
Ok(comment) => comment, Ok(comment) => comment,
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()), Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
}; };
// Send the apub message // Send the apub message
if deleted { if deleted {
updated_comment updated_comment.send_delete(&user, context).await?;
.send_delete(&local_user_view.person, context)
.await?;
} else { } else {
updated_comment updated_comment.send_undo_delete(&user, context).await?;
.send_undo_delete(&local_user_view.person, context)
.await?;
} }
// Refetch it // Refetch it
let comment_id = data.comment_id; let edit_id = data.edit_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let comment_view = blocking(context.pool(), move |conn| { let comment_view = blocking(context.pool(), move |conn| {
CommentView::read(conn, comment_id, Some(person_id)) CommentView::read(conn, edit_id, Some(user_id))
}) })
.await??; .await??;
@ -340,7 +303,7 @@ impl Perform for DeleteComment {
let recipient_ids = send_local_notifs( let recipient_ids = send_local_notifs(
mentions, mentions,
updated_comment, updated_comment,
local_user_view.person.clone(), &user,
comment_view_2.post, comment_view_2.post,
context.pool(), context.pool(),
false, false,
@ -373,44 +336,34 @@ impl Perform for RemoveComment {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> { ) -> Result<CommentResponse, LemmyError> {
let data: &RemoveComment = &self; let data: &RemoveComment = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let comment_id = data.comment_id; let edit_id = data.edit_id;
let orig_comment = blocking(context.pool(), move |conn| { let orig_comment = blocking(context.pool(), move |conn| {
CommentView::read(&conn, comment_id, None) CommentView::read(&conn, edit_id, None)
}) })
.await??; .await??;
check_community_ban( check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
local_user_view.person.id,
orig_comment.community.id,
context.pool(),
)
.await?;
// Verify that only a mod or admin can remove // Verify that only a mod or admin can remove
is_mod_or_admin( is_mod_or_admin(context.pool(), user.id, orig_comment.community.id).await?;
context.pool(),
local_user_view.person.id,
orig_comment.community.id,
)
.await?;
// Do the remove // Do the remove
let removed = data.removed; let removed = data.removed;
let updated_comment = match blocking(context.pool(), move |conn| { let updated_comment = match blocking(context.pool(), move |conn| {
Comment::update_removed(conn, comment_id, removed) Comment::update_removed(conn, edit_id, removed)
}) })
.await? .await?
{ {
Ok(comment) => comment, Ok(comment) => comment,
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()), Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
}; };
// Mod tables // Mod tables
let form = ModRemoveCommentForm { let form = ModRemoveCommentForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
comment_id: data.comment_id, comment_id: data.edit_id,
removed: Some(removed), removed: Some(removed),
reason: data.reason.to_owned(), reason: data.reason.to_owned(),
}; };
@ -421,20 +374,16 @@ impl Perform for RemoveComment {
// Send the apub message // Send the apub message
if removed { if removed {
updated_comment updated_comment.send_remove(&user, context).await?;
.send_remove(&local_user_view.person, context)
.await?;
} else { } else {
updated_comment updated_comment.send_undo_remove(&user, context).await?;
.send_undo_remove(&local_user_view.person, context)
.await?;
} }
// Refetch it // Refetch it
let comment_id = data.comment_id; let edit_id = data.edit_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let comment_view = blocking(context.pool(), move |conn| { let comment_view = blocking(context.pool(), move |conn| {
CommentView::read(conn, comment_id, Some(person_id)) CommentView::read(conn, edit_id, Some(user_id))
}) })
.await??; .await??;
@ -445,7 +394,7 @@ impl Perform for RemoveComment {
let recipient_ids = send_local_notifs( let recipient_ids = send_local_notifs(
mentions, mentions,
updated_comment, updated_comment,
local_user_view.person.clone(), &user,
comment_view_2.post, comment_view_2.post,
context.pool(), context.pool(),
false, false,
@ -478,7 +427,7 @@ impl Perform for MarkCommentAsRead {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> { ) -> Result<CommentResponse, LemmyError> {
let data: &MarkCommentAsRead = &self; let data: &MarkCommentAsRead = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let comment_id = data.comment_id; let comment_id = data.comment_id;
let orig_comment = blocking(context.pool(), move |conn| { let orig_comment = blocking(context.pool(), move |conn| {
@ -486,16 +435,11 @@ impl Perform for MarkCommentAsRead {
}) })
.await??; .await??;
check_community_ban( check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
local_user_view.person.id,
orig_comment.community.id,
context.pool(),
)
.await?;
// Verify that only the recipient can mark as read // Verify that only the recipient can mark as read
if local_user_view.person.id != orig_comment.get_recipient_id() { if user.id != orig_comment.get_recipient_id() {
return Err(ApiError::err("no_comment_edit_allowed").into()); return Err(APIError::err("no_comment_edit_allowed").into());
} }
// Do the mark as read // Do the mark as read
@ -506,14 +450,14 @@ impl Perform for MarkCommentAsRead {
.await? .await?
{ {
Ok(comment) => comment, Ok(comment) => comment,
Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()), Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
}; };
// Refetch it // Refetch it
let comment_id = data.comment_id; let edit_id = data.comment_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let comment_view = blocking(context.pool(), move |conn| { let comment_view = blocking(context.pool(), move |conn| {
CommentView::read(conn, comment_id, Some(person_id)) CommentView::read(conn, edit_id, Some(user_id))
}) })
.await??; .await??;
@ -537,29 +481,29 @@ impl Perform for SaveComment {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> { ) -> Result<CommentResponse, LemmyError> {
let data: &SaveComment = &self; let data: &SaveComment = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let comment_saved_form = CommentSavedForm { let comment_saved_form = CommentSavedForm {
comment_id: data.comment_id, comment_id: data.comment_id,
person_id: local_user_view.person.id, user_id: user.id,
}; };
if data.save { if data.save {
let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form); let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
if blocking(context.pool(), save_comment).await?.is_err() { if blocking(context.pool(), save_comment).await?.is_err() {
return Err(ApiError::err("couldnt_save_comment").into()); return Err(APIError::err("couldnt_save_comment").into());
} }
} else { } else {
let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form); let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
if blocking(context.pool(), unsave_comment).await?.is_err() { if blocking(context.pool(), unsave_comment).await?.is_err() {
return Err(ApiError::err("couldnt_save_comment").into()); return Err(APIError::err("couldnt_save_comment").into());
} }
} }
let comment_id = data.comment_id; let comment_id = data.comment_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let comment_view = blocking(context.pool(), move |conn| { let comment_view = blocking(context.pool(), move |conn| {
CommentView::read(conn, comment_id, Some(person_id)) CommentView::read(conn, comment_id, Some(user_id))
}) })
.await??; .await??;
@ -581,9 +525,9 @@ impl Perform for CreateCommentLike {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> { ) -> Result<CommentResponse, LemmyError> {
let data: &CreateCommentLike = &self; let data: &CreateCommentLike = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let mut recipient_ids = Vec::<LocalUserId>::new(); let mut recipient_ids = Vec::new();
// Don't do a downvote if site has downvotes disabled // Don't do a downvote if site has downvotes disabled
check_downvotes_enabled(data.score, context.pool()).await?; check_downvotes_enabled(data.score, context.pool()).await?;
@ -594,34 +538,22 @@ impl Perform for CreateCommentLike {
}) })
.await??; .await??;
check_community_ban( check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
local_user_view.person.id,
orig_comment.community.id,
context.pool(),
)
.await?;
// Add parent user to recipients // Add parent user to recipients
let recipient_id = orig_comment.get_recipient_id(); recipient_ids.push(orig_comment.get_recipient_id());
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
LocalUserView::read_person(conn, recipient_id)
})
.await?
{
recipient_ids.push(local_recipient.local_user.id);
}
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
comment_id: data.comment_id, comment_id: data.comment_id,
post_id: orig_comment.post.id, post_id: orig_comment.post.id,
person_id: local_user_view.person.id, user_id: user.id,
score: data.score, score: data.score,
}; };
// Remove any likes first // Remove any likes first
let person_id = local_user_view.person.id; let user_id = user.id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
CommentLike::remove(conn, person_id, comment_id) CommentLike::remove(conn, user_id, comment_id)
}) })
.await??; .await??;
@ -632,27 +564,23 @@ impl Perform for CreateCommentLike {
let like_form2 = like_form.clone(); let like_form2 = like_form.clone();
let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2); let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
if blocking(context.pool(), like).await?.is_err() { if blocking(context.pool(), like).await?.is_err() {
return Err(ApiError::err("couldnt_like_comment").into()); return Err(APIError::err("couldnt_like_comment").into());
} }
if like_form.score == 1 { if like_form.score == 1 {
comment.send_like(&local_user_view.person, context).await?; comment.send_like(&user, context).await?;
} else if like_form.score == -1 { } else if like_form.score == -1 {
comment comment.send_dislike(&user, context).await?;
.send_dislike(&local_user_view.person, context)
.await?;
} }
} else { } else {
comment comment.send_undo_like(&user, context).await?;
.send_undo_like(&local_user_view.person, context)
.await?;
} }
// Have to refetch the comment to get the current state // Have to refetch the comment to get the current state
let comment_id = data.comment_id; let comment_id = data.comment_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let liked_comment = blocking(context.pool(), move |conn| { let liked_comment = blocking(context.pool(), move |conn| {
CommentView::read(conn, comment_id, Some(person_id)) CommentView::read(conn, comment_id, Some(user_id))
}) })
.await??; .await??;
@ -682,8 +610,8 @@ impl Perform for GetComments {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetCommentsResponse, LemmyError> { ) -> Result<GetCommentsResponse, LemmyError> {
let data: &GetComments = &self; let data: &GetComments = &self;
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
let person_id = local_user_view.map(|u| u.person.id); let user_id = user.map(|u| u.id);
let type_ = ListingType::from_str(&data.type_)?; let type_ = ListingType::from_str(&data.type_)?;
let sort = SortType::from_str(&data.sort)?; let sort = SortType::from_str(&data.sort)?;
@ -698,7 +626,7 @@ impl Perform for GetComments {
.sort(&sort) .sort(&sort)
.community_id(community_id) .community_id(community_id)
.community_name(community_name) .community_name(community_name)
.my_person_id(person_id) .my_user_id(user_id)
.page(page) .page(page)
.limit(limit) .limit(limit)
.list() .list()
@ -706,7 +634,7 @@ impl Perform for GetComments {
.await?; .await?;
let comments = match comments { let comments = match comments {
Ok(comments) => comments, Ok(comments) => comments,
Err(_) => return Err(ApiError::err("couldnt_get_comments").into()), Err(_) => return Err(APIError::err("couldnt_get_comments").into()),
}; };
Ok(GetCommentsResponse { comments }) Ok(GetCommentsResponse { comments })
@ -724,28 +652,28 @@ impl Perform for CreateCommentReport {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CreateCommentReportResponse, LemmyError> { ) -> Result<CreateCommentReportResponse, LemmyError> {
let data: &CreateCommentReport = &self; let data: &CreateCommentReport = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// check size of report and check for whitespace // check size of report and check for whitespace
let reason = data.reason.trim(); let reason = data.reason.trim();
if reason.is_empty() { if reason.is_empty() {
return Err(ApiError::err("report_reason_required").into()); return Err(APIError::err("report_reason_required").into());
} }
if reason.chars().count() > 1000 { if reason.len() > 1000 {
return Err(ApiError::err("report_too_long").into()); return Err(APIError::err("report_too_long").into());
} }
let person_id = local_user_view.person.id; let user_id = user.id;
let comment_id = data.comment_id; let comment_id = data.comment_id;
let comment_view = blocking(context.pool(), move |conn| { let comment_view = blocking(context.pool(), move |conn| {
CommentView::read(&conn, comment_id, None) CommentView::read(&conn, comment_id, None)
}) })
.await??; .await??;
check_community_ban(person_id, comment_view.community.id, context.pool()).await?; check_community_ban(user_id, comment_view.community.id, context.pool()).await?;
let report_form = CommentReportForm { let report_form = CommentReportForm {
creator_id: person_id, creator_id: user_id,
comment_id, comment_id,
original_comment_text: comment_view.comment.content, original_comment_text: comment_view.comment.content,
reason: data.reason.to_owned(), reason: data.reason.to_owned(),
@ -757,7 +685,7 @@ impl Perform for CreateCommentReport {
.await? .await?
{ {
Ok(report) => report, Ok(report) => report,
Err(_e) => return Err(ApiError::err("couldnt_create_report").into()), Err(_e) => return Err(APIError::err("couldnt_create_report").into()),
}; };
let res = CreateCommentReportResponse { success: true }; let res = CreateCommentReportResponse { success: true };
@ -765,7 +693,7 @@ impl Perform for CreateCommentReport {
context.chat_server().do_send(SendUserRoomMessage { context.chat_server().do_send(SendUserRoomMessage {
op: UserOperation::CreateCommentReport, op: UserOperation::CreateCommentReport,
response: res.clone(), response: res.clone(),
local_recipient_id: local_user_view.local_user.id, recipient_id: user.id,
websocket_id, websocket_id,
}); });
@ -791,7 +719,7 @@ impl Perform for ResolveCommentReport {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<ResolveCommentReportResponse, LemmyError> { ) -> Result<ResolveCommentReportResponse, LemmyError> {
let data: &ResolveCommentReport = &self; let data: &ResolveCommentReport = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let report_id = data.report_id; let report_id = data.report_id;
let report = blocking(context.pool(), move |conn| { let report = blocking(context.pool(), move |conn| {
@ -799,20 +727,20 @@ impl Perform for ResolveCommentReport {
}) })
.await??; .await??;
let person_id = local_user_view.person.id; let user_id = user.id;
is_mod_or_admin(context.pool(), person_id, report.community.id).await?; is_mod_or_admin(context.pool(), user_id, report.community.id).await?;
let resolved = data.resolved; let resolved = data.resolved;
let resolve_fun = move |conn: &'_ _| { let resolve_fun = move |conn: &'_ _| {
if resolved { if resolved {
CommentReport::resolve(conn, report_id, person_id) CommentReport::resolve(conn, report_id, user_id)
} else { } else {
CommentReport::unresolve(conn, report_id, person_id) CommentReport::unresolve(conn, report_id, user_id)
} }
}; };
if blocking(context.pool(), resolve_fun).await?.is_err() { if blocking(context.pool(), resolve_fun).await?.is_err() {
return Err(ApiError::err("couldnt_resolve_report").into()); return Err(APIError::err("couldnt_resolve_report").into());
}; };
let report_id = data.report_id; let report_id = data.report_id;
@ -844,12 +772,12 @@ impl Perform for ListCommentReports {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<ListCommentReportsResponse, LemmyError> { ) -> Result<ListCommentReportsResponse, LemmyError> {
let data: &ListCommentReports = &self; let data: &ListCommentReports = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let person_id = local_user_view.person.id; let user_id = user.id;
let community_id = data.community; let community_id = data.community;
let community_ids = let community_ids =
collect_moderated_communities(person_id, community_id, context.pool()).await?; collect_moderated_communities(user_id, community_id, context.pool()).await?;
let page = data.page; let page = data.page;
let limit = data.limit; let limit = data.limit;
@ -867,7 +795,7 @@ impl Perform for ListCommentReports {
context.chat_server().do_send(SendUserRoomMessage { context.chat_server().do_send(SendUserRoomMessage {
op: UserOperation::ListCommentReports, op: UserOperation::ListCommentReports,
response: res.clone(), response: res.clone(),
local_recipient_id: local_user_view.local_user.id, recipient_id: user.id,
websocket_id, websocket_id,
}); });

View file

@ -1,24 +1,16 @@
use crate::{ use crate::{
check_community_ban, check_optional_url,
get_local_user_view_from_jwt, get_user_from_jwt,
get_local_user_view_from_jwt_opt, get_user_from_jwt_opt,
is_admin, is_admin,
is_mod_or_admin, is_mod_or_admin,
Perform, Perform,
}; };
use actix_web::web::Data; use actix_web::web::Data;
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::{blocking, community::*}; use lemmy_apub::ActorType;
use lemmy_apub::{
generate_apub_endpoint,
generate_followers_url,
generate_inbox_url,
generate_shared_inbox_url,
ActorType,
EndpointType,
};
use lemmy_db_queries::{ use lemmy_db_queries::{
diesel_option_overwrite_to_url, diesel_option_overwrite,
source::{ source::{
comment::Comment_, comment::Comment_,
community::{CommunityModerator_, Community_}, community::{CommunityModerator_, Community_},
@ -29,31 +21,30 @@ use lemmy_db_queries::{
Crud, Crud,
Followable, Followable,
Joinable, Joinable,
ListingType,
SortType, SortType,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
naive_now, naive_now,
source::{comment::Comment, community::*, moderator::*, post::Post, site::*}, source::{comment::Comment, community::*, moderator::*, post::Post, site::*},
PersonId,
}; };
use lemmy_db_views::comment_view::CommentQueryBuilder; use lemmy_db_views::comment_view::CommentQueryBuilder;
use lemmy_db_views_actor::{ use lemmy_db_views_actor::{
community_follower_view::CommunityFollowerView, community_follower_view::CommunityFollowerView,
community_moderator_view::CommunityModeratorView, community_moderator_view::CommunityModeratorView,
community_view::{CommunityQueryBuilder, CommunityView}, community_view::{CommunityQueryBuilder, CommunityView},
person_view::PersonViewSafe, user_view::UserViewSafe,
}; };
use lemmy_structs::{blocking, community::*};
use lemmy_utils::{ use lemmy_utils::{
apub::generate_actor_keypair, apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
location_info, location_info,
utils::{check_slurs, check_slurs_opt, is_valid_community_name, naive_from_unix}, utils::{check_slurs, check_slurs_opt, is_valid_community_name, naive_from_unix},
ApiError, APIError,
ConnectionId, ConnectionId,
LemmyError, LemmyError,
}; };
use lemmy_websocket::{ use lemmy_websocket::{
messages::{GetCommunityUsersOnline, SendCommunityRoomMessage}, messages::{GetCommunityUsersOnline, JoinCommunityRoom, JoinModRoom, SendCommunityRoomMessage},
LemmyContext, LemmyContext,
UserOperation, UserOperation,
}; };
@ -69,8 +60,8 @@ impl Perform for GetCommunity {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetCommunityResponse, LemmyError> { ) -> Result<GetCommunityResponse, LemmyError> {
let data: &GetCommunity = &self; let data: &GetCommunity = &self;
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
let person_id = local_user_view.map(|u| u.person.id); let user_id = user.map(|u| u.id);
let community_id = match data.id { let community_id = match data.id {
Some(id) => id, Some(id) => id,
@ -82,19 +73,19 @@ impl Perform for GetCommunity {
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
} }
.id .id
} }
}; };
let community_view = match blocking(context.pool(), move |conn| { let community_view = match blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, person_id) CommunityView::read(conn, community_id, user_id)
}) })
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}; };
let moderators: Vec<CommunityModeratorView> = match blocking(context.pool(), move |conn| { let moderators: Vec<CommunityModeratorView> = match blocking(context.pool(), move |conn| {
@ -103,7 +94,7 @@ impl Perform for GetCommunity {
.await? .await?
{ {
Ok(moderators) => moderators, Ok(moderators) => moderators,
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}; };
let online = context let online = context
@ -133,30 +124,33 @@ impl Perform for CreateCommunity {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<CommunityResponse, LemmyError> {
let data: &CreateCommunity = &self; let data: &CreateCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
check_slurs(&data.name)?; check_slurs(&data.name)?;
check_slurs(&data.title)?; check_slurs(&data.title)?;
check_slurs_opt(&data.description)?; check_slurs_opt(&data.description)?;
if !is_valid_community_name(&data.name) { if !is_valid_community_name(&data.name) {
return Err(ApiError::err("invalid_community_name").into()); return Err(APIError::err("invalid_community_name").into());
} }
// Double check for duplicate community actor_ids // Double check for duplicate community actor_ids
let community_actor_id = generate_apub_endpoint(EndpointType::Community, &data.name)?; let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string();
let actor_id_cloned = community_actor_id.to_owned(); let actor_id_cloned = actor_id.to_owned();
let community_dupe = blocking(context.pool(), move |conn| { let community_dupe = blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &actor_id_cloned) Community::read_from_apub_id(conn, &actor_id_cloned)
}) })
.await?; .await?;
if community_dupe.is_ok() { if community_dupe.is_ok() {
return Err(ApiError::err("community_already_exists").into()); return Err(APIError::err("community_already_exists").into());
} }
// Check to make sure the icon and banners are urls // Check to make sure the icon and banners are urls
let icon = diesel_option_overwrite_to_url(&data.icon)?; let icon = diesel_option_overwrite(&data.icon);
let banner = diesel_option_overwrite_to_url(&data.banner)?; let banner = diesel_option_overwrite(&data.banner);
check_optional_url(&icon)?;
check_optional_url(&banner)?;
// When you create a community, make sure the user becomes a moderator and a follower // When you create a community, make sure the user becomes a moderator and a follower
let keypair = generate_actor_keypair()?; let keypair = generate_actor_keypair()?;
@ -167,20 +161,18 @@ impl Perform for CreateCommunity {
description: data.description.to_owned(), description: data.description.to_owned(),
icon, icon,
banner, banner,
creator_id: local_user_view.person.id, category_id: data.category_id,
creator_id: user.id,
removed: None, removed: None,
deleted: None, deleted: None,
nsfw: data.nsfw, nsfw: data.nsfw,
updated: None, updated: None,
actor_id: Some(community_actor_id.to_owned()), actor_id: Some(actor_id),
local: true, local: true,
private_key: Some(keypair.private_key), private_key: Some(keypair.private_key),
public_key: Some(keypair.public_key), public_key: Some(keypair.public_key),
last_refreshed_at: None, last_refreshed_at: None,
published: None, published: None,
followers_url: Some(generate_followers_url(&community_actor_id)?),
inbox_url: Some(generate_inbox_url(&community_actor_id)?),
shared_inbox_url: Some(Some(generate_shared_inbox_url(&community_actor_id)?)),
}; };
let inserted_community = match blocking(context.pool(), move |conn| { let inserted_community = match blocking(context.pool(), move |conn| {
@ -189,35 +181,35 @@ impl Perform for CreateCommunity {
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("community_already_exists").into()), Err(_e) => return Err(APIError::err("community_already_exists").into()),
}; };
// The community creator becomes a moderator // The community creator becomes a moderator
let community_moderator_form = CommunityModeratorForm { let community_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: local_user_view.person.id, user_id: user.id,
}; };
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
if blocking(context.pool(), join).await?.is_err() { if blocking(context.pool(), join).await?.is_err() {
return Err(ApiError::err("community_moderator_already_exists").into()); return Err(APIError::err("community_moderator_already_exists").into());
} }
// Follow your own community // Follow your own community
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: local_user_view.person.id, user_id: user.id,
pending: false, pending: false,
}; };
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form); let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
if blocking(context.pool(), follow).await?.is_err() { if blocking(context.pool(), follow).await?.is_err() {
return Err(ApiError::err("community_follower_already_exists").into()); return Err(APIError::err("community_follower_already_exists").into());
} }
let person_id = local_user_view.person.id; let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| { let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, inserted_community.id, Some(person_id)) CommunityView::read(conn, inserted_community.id, Some(user_id))
}) })
.await??; .await??;
@ -235,30 +227,31 @@ impl Perform for EditCommunity {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<CommunityResponse, LemmyError> {
let data: &EditCommunity = &self; let data: &EditCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
check_slurs(&data.title)?; check_slurs(&data.title)?;
check_slurs_opt(&data.description)?; check_slurs_opt(&data.description)?;
// Verify its a mod (only mods can edit it) // Verify its a mod (only mods can edit it)
let community_id = data.community_id; let edit_id = data.edit_id;
let mods: Vec<PersonId> = blocking(context.pool(), move |conn| { let mods: Vec<i32> = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id) CommunityModeratorView::for_community(conn, edit_id)
.map(|v| v.into_iter().map(|m| m.moderator.id).collect()) .map(|v| v.into_iter().map(|m| m.moderator.id).collect())
}) })
.await??; .await??;
if !mods.contains(&local_user_view.person.id) { if !mods.contains(&user.id) {
return Err(ApiError::err("not_a_moderator").into()); return Err(APIError::err("not_a_moderator").into());
} }
let community_id = data.community_id; let edit_id = data.edit_id;
let read_community = blocking(context.pool(), move |conn| { let read_community =
Community::read(conn, community_id) blocking(context.pool(), move |conn| Community::read(conn, edit_id)).await??;
})
.await??;
let icon = diesel_option_overwrite_to_url(&data.icon)?; let icon = diesel_option_overwrite(&data.icon);
let banner = diesel_option_overwrite_to_url(&data.banner)?; let banner = diesel_option_overwrite(&data.banner);
check_optional_url(&icon)?;
check_optional_url(&banner)?;
let community_form = CommunityForm { let community_form = CommunityForm {
name: read_community.name, name: read_community.name,
@ -266,6 +259,7 @@ impl Perform for EditCommunity {
description: data.description.to_owned(), description: data.description.to_owned(),
icon, icon,
banner, banner,
category_id: data.category_id.to_owned(),
creator_id: read_community.creator_id, creator_id: read_community.creator_id,
removed: Some(read_community.removed), removed: Some(read_community.removed),
deleted: Some(read_community.deleted), deleted: Some(read_community.deleted),
@ -277,28 +271,25 @@ impl Perform for EditCommunity {
public_key: read_community.public_key, public_key: read_community.public_key,
last_refreshed_at: None, last_refreshed_at: None,
published: None, published: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let community_id = data.community_id; let edit_id = data.edit_id;
match blocking(context.pool(), move |conn| { match blocking(context.pool(), move |conn| {
Community::update(conn, community_id, &community_form) Community::update(conn, edit_id, &community_form)
}) })
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()), Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
}; };
// TODO there needs to be some kind of an apub update // TODO there needs to be some kind of an apub update
// process for communities and users // process for communities and users
let community_id = data.community_id; let edit_id = data.edit_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| { let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(person_id)) CommunityView::read(conn, edit_id, Some(user_id))
}) })
.await??; .await??;
@ -320,28 +311,26 @@ impl Perform for DeleteCommunity {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<CommunityResponse, LemmyError> {
let data: &DeleteCommunity = &self; let data: &DeleteCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Verify its the creator (only a creator can delete the community) // Verify its the creator (only a creator can delete the community)
let community_id = data.community_id; let edit_id = data.edit_id;
let read_community = blocking(context.pool(), move |conn| { let read_community =
Community::read(conn, community_id) blocking(context.pool(), move |conn| Community::read(conn, edit_id)).await??;
}) if read_community.creator_id != user.id {
.await??; return Err(APIError::err("no_community_edit_allowed").into());
if read_community.creator_id != local_user_view.person.id {
return Err(ApiError::err("no_community_edit_allowed").into());
} }
// Do the delete // Do the delete
let community_id = data.community_id; let edit_id = data.edit_id;
let deleted = data.deleted; let deleted = data.deleted;
let updated_community = match blocking(context.pool(), move |conn| { let updated_community = match blocking(context.pool(), move |conn| {
Community::update_deleted(conn, community_id, deleted) Community::update_deleted(conn, edit_id, deleted)
}) })
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()), Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
}; };
// Send apub messages // Send apub messages
@ -351,10 +340,10 @@ impl Perform for DeleteCommunity {
updated_community.send_undo_delete(context).await?; updated_community.send_undo_delete(context).await?;
} }
let community_id = data.community_id; let edit_id = data.edit_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| { let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(person_id)) CommunityView::read(conn, edit_id, Some(user_id))
}) })
.await??; .await??;
@ -376,21 +365,21 @@ impl Perform for RemoveCommunity {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<CommunityResponse, LemmyError> {
let data: &RemoveCommunity = &self; let data: &RemoveCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Verify its an admin (only an admin can remove a community) // Verify its an admin (only an admin can remove a community)
is_admin(&local_user_view)?; is_admin(context.pool(), user.id).await?;
// Do the remove // Do the remove
let community_id = data.community_id; let edit_id = data.edit_id;
let removed = data.removed; let removed = data.removed;
let updated_community = match blocking(context.pool(), move |conn| { let updated_community = match blocking(context.pool(), move |conn| {
Community::update_removed(conn, community_id, removed) Community::update_removed(conn, edit_id, removed)
}) })
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()), Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
}; };
// Mod tables // Mod tables
@ -399,8 +388,8 @@ impl Perform for RemoveCommunity {
None => None, None => None,
}; };
let form = ModRemoveCommunityForm { let form = ModRemoveCommunityForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
community_id: data.community_id, community_id: data.edit_id,
removed: Some(removed), removed: Some(removed),
reason: data.reason.to_owned(), reason: data.reason.to_owned(),
expires, expires,
@ -417,10 +406,10 @@ impl Perform for RemoveCommunity {
updated_community.send_undo_remove(context).await?; updated_community.send_undo_remove(context).await?;
} }
let community_id = data.community_id; let edit_id = data.edit_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| { let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(person_id)) CommunityView::read(conn, edit_id, Some(user_id))
}) })
.await??; .await??;
@ -442,30 +431,27 @@ impl Perform for ListCommunities {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<ListCommunitiesResponse, LemmyError> { ) -> Result<ListCommunitiesResponse, LemmyError> {
let data: &ListCommunities = &self; let data: &ListCommunities = &self;
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
let person_id = match &local_user_view { let user_id = match &user {
Some(uv) => Some(uv.person.id), Some(user) => Some(user.id),
None => None, None => None,
}; };
// Don't show NSFW by default let show_nsfw = match &user {
let show_nsfw = match &local_user_view { Some(user) => user.show_nsfw,
Some(uv) => uv.local_user.show_nsfw,
None => false, None => false,
}; };
let type_ = ListingType::from_str(&data.type_)?;
let sort = SortType::from_str(&data.sort)?; let sort = SortType::from_str(&data.sort)?;
let page = data.page; let page = data.page;
let limit = data.limit; let limit = data.limit;
let communities = blocking(context.pool(), move |conn| { let communities = blocking(context.pool(), move |conn| {
CommunityQueryBuilder::create(conn) CommunityQueryBuilder::create(conn)
.listing_type(&type_)
.sort(&sort) .sort(&sort)
.show_nsfw(show_nsfw) .show_nsfw(show_nsfw)
.my_person_id(person_id) .my_user_id(user_id)
.page(page) .page(page)
.limit(limit) .limit(limit)
.list() .list()
@ -487,7 +473,7 @@ impl Perform for FollowCommunity {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<CommunityResponse, LemmyError> {
let data: &FollowCommunity = &self; let data: &FollowCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id; let community_id = data.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
@ -496,47 +482,39 @@ impl Perform for FollowCommunity {
.await??; .await??;
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: data.community_id, community_id: data.community_id,
person_id: local_user_view.person.id, user_id: user.id,
pending: false, pending: false,
}; };
if community.local { if community.local {
if data.follow { if data.follow {
check_community_ban(local_user_view.person.id, community_id, context.pool()).await?;
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form); let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
if blocking(context.pool(), follow).await?.is_err() { if blocking(context.pool(), follow).await?.is_err() {
return Err(ApiError::err("community_follower_already_exists").into()); return Err(APIError::err("community_follower_already_exists").into());
} }
} else { } else {
let unfollow = let unfollow =
move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form); move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
if blocking(context.pool(), unfollow).await?.is_err() { if blocking(context.pool(), unfollow).await?.is_err() {
return Err(ApiError::err("community_follower_already_exists").into()); return Err(APIError::err("community_follower_already_exists").into());
} }
} }
} else if data.follow { } else if data.follow {
// Dont actually add to the community followers here, because you need // Dont actually add to the community followers here, because you need
// to wait for the accept // to wait for the accept
local_user_view user.send_follow(&community.actor_id()?, context).await?;
.person
.send_follow(&community.actor_id(), context)
.await?;
} else { } else {
local_user_view user.send_unfollow(&community.actor_id()?, context).await?;
.person
.send_unfollow(&community.actor_id(), context)
.await?;
let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form); let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
if blocking(context.pool(), unfollow).await?.is_err() { if blocking(context.pool(), unfollow).await?.is_err() {
return Err(ApiError::err("community_follower_already_exists").into()); return Err(APIError::err("community_follower_already_exists").into());
} }
} }
let community_id = data.community_id; let community_id = data.community_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let mut community_view = blocking(context.pool(), move |conn| { let mut community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(person_id)) CommunityView::read(conn, community_id, Some(user_id))
}) })
.await??; .await??;
@ -561,16 +539,16 @@ impl Perform for GetFollowedCommunities {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetFollowedCommunitiesResponse, LemmyError> { ) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
let data: &GetFollowedCommunities = &self; let data: &GetFollowedCommunities = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let person_id = local_user_view.person.id; let user_id = user.id;
let communities = match blocking(context.pool(), move |conn| { let communities = match blocking(context.pool(), move |conn| {
CommunityFollowerView::for_person(conn, person_id) CommunityFollowerView::for_user(conn, user_id)
}) })
.await? .await?
{ {
Ok(communities) => communities, Ok(communities) => communities,
_ => return Err(ApiError::err("system_err_login").into()), _ => return Err(APIError::err("system_err_login").into()),
}; };
// Return the jwt // Return the jwt
@ -588,40 +566,28 @@ impl Perform for BanFromCommunity {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<BanFromCommunityResponse, LemmyError> { ) -> Result<BanFromCommunityResponse, LemmyError> {
let data: &BanFromCommunity = &self; let data: &BanFromCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id; let community_id = data.community_id;
let banned_person_id = data.person_id; let banned_user_id = data.user_id;
// Verify that only mods or admins can ban // Verify that only mods or admins can ban
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?; is_mod_or_admin(context.pool(), user.id, community_id).await?;
let community_user_ban_form = CommunityPersonBanForm { let community_user_ban_form = CommunityUserBanForm {
community_id: data.community_id, community_id: data.community_id,
person_id: data.person_id, user_id: data.user_id,
}; };
if data.ban { if data.ban {
let ban = move |conn: &'_ _| CommunityPersonBan::ban(conn, &community_user_ban_form); let ban = move |conn: &'_ _| CommunityUserBan::ban(conn, &community_user_ban_form);
if blocking(context.pool(), ban).await?.is_err() { if blocking(context.pool(), ban).await?.is_err() {
return Err(ApiError::err("community_user_already_banned").into()); return Err(APIError::err("community_user_already_banned").into());
} }
// Also unsubscribe them from the community, if they are subscribed
let community_follower_form = CommunityFollowerForm {
community_id: data.community_id,
person_id: banned_person_id,
pending: false,
};
blocking(context.pool(), move |conn: &'_ _| {
CommunityFollower::unfollow(conn, &community_follower_form)
})
.await?
.ok();
} else { } else {
let unban = move |conn: &'_ _| CommunityPersonBan::unban(conn, &community_user_ban_form); let unban = move |conn: &'_ _| CommunityUserBan::unban(conn, &community_user_ban_form);
if blocking(context.pool(), unban).await?.is_err() { if blocking(context.pool(), unban).await?.is_err() {
return Err(ApiError::err("community_user_already_banned").into()); return Err(APIError::err("community_user_already_banned").into());
} }
} }
@ -629,7 +595,7 @@ impl Perform for BanFromCommunity {
if data.remove_data { if data.remove_data {
// Posts // Posts
blocking(context.pool(), move |conn: &'_ _| { blocking(context.pool(), move |conn: &'_ _| {
Post::update_removed_for_creator(conn, banned_person_id, Some(community_id), true) Post::update_removed_for_creator(conn, banned_user_id, Some(community_id), true)
}) })
.await??; .await??;
@ -637,7 +603,7 @@ impl Perform for BanFromCommunity {
// TODO Diesel doesn't allow updates with joins, so this has to be a loop // TODO Diesel doesn't allow updates with joins, so this has to be a loop
let comments = blocking(context.pool(), move |conn| { let comments = blocking(context.pool(), move |conn| {
CommentQueryBuilder::create(conn) CommentQueryBuilder::create(conn)
.creator_id(banned_person_id) .creator_id(banned_user_id)
.community_id(community_id) .community_id(community_id)
.limit(std::i64::MAX) .limit(std::i64::MAX)
.list() .list()
@ -661,8 +627,8 @@ impl Perform for BanFromCommunity {
}; };
let form = ModBanFromCommunityForm { let form = ModBanFromCommunityForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
other_person_id: data.person_id, other_user_id: data.user_id,
community_id: data.community_id, community_id: data.community_id,
reason: data.reason.to_owned(), reason: data.reason.to_owned(),
banned: Some(data.ban), banned: Some(data.ban),
@ -673,14 +639,14 @@ impl Perform for BanFromCommunity {
}) })
.await??; .await??;
let person_id = data.person_id; let user_id = data.user_id;
let person_view = blocking(context.pool(), move |conn| { let user_view = blocking(context.pool(), move |conn| {
PersonViewSafe::read(conn, person_id) UserViewSafe::read(conn, user_id)
}) })
.await??; .await??;
let res = BanFromCommunityResponse { let res = BanFromCommunityResponse {
person_view, user_view,
banned: data.ban, banned: data.ban,
}; };
@ -705,34 +671,34 @@ impl Perform for AddModToCommunity {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<AddModToCommunityResponse, LemmyError> { ) -> Result<AddModToCommunityResponse, LemmyError> {
let data: &AddModToCommunity = &self; let data: &AddModToCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_moderator_form = CommunityModeratorForm { let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id, community_id: data.community_id,
person_id: data.person_id, user_id: data.user_id,
}; };
let community_id = data.community_id; let community_id = data.community_id;
// Verify that only mods or admins can add mod // Verify that only mods or admins can add mod
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?; is_mod_or_admin(context.pool(), user.id, community_id).await?;
if data.added { if data.added {
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
if blocking(context.pool(), join).await?.is_err() { if blocking(context.pool(), join).await?.is_err() {
return Err(ApiError::err("community_moderator_already_exists").into()); return Err(APIError::err("community_moderator_already_exists").into());
} }
} else { } else {
let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form); let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
if blocking(context.pool(), leave).await?.is_err() { if blocking(context.pool(), leave).await?.is_err() {
return Err(ApiError::err("community_moderator_already_exists").into()); return Err(APIError::err("community_moderator_already_exists").into());
} }
} }
// Mod tables // Mod tables
let form = ModAddCommunityForm { let form = ModAddCommunityForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
other_person_id: data.person_id, other_user_id: data.user_id,
community_id: data.community_id, community_id: data.community_id,
removed: Some(!data.added), removed: Some(!data.added),
}; };
@ -770,7 +736,7 @@ impl Perform for TransferCommunity {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetCommunityResponse, LemmyError> { ) -> Result<GetCommunityResponse, LemmyError> {
let data: &TransferCommunity = &self; let data: &TransferCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id; let community_id = data.community_id;
let read_community = blocking(context.pool(), move |conn| { let read_community = blocking(context.pool(), move |conn| {
@ -783,31 +749,28 @@ impl Perform for TransferCommunity {
}) })
.await??; .await??;
let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??; let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
// Making sure the creator, if an admin, is at the top // Making sure the creator, if an admin, is at the top
let creator_index = admins let creator_index = admins
.iter() .iter()
.position(|r| r.person.id == site_creator_id) .position(|r| r.user.id == site_creator_id)
.context(location_info!())?; .context(location_info!())?;
let creator_person = admins.remove(creator_index); let creator_user = admins.remove(creator_index);
admins.insert(0, creator_person); admins.insert(0, creator_user);
// Make sure user is the creator, or an admin // Make sure user is the creator, or an admin
if local_user_view.person.id != read_community.creator_id if user.id != read_community.creator_id
&& !admins && !admins.iter().map(|a| a.user.id).any(|x| x == user.id)
.iter()
.map(|a| a.person.id)
.any(|x| x == local_user_view.person.id)
{ {
return Err(ApiError::err("not_an_admin").into()); return Err(APIError::err("not_an_admin").into());
} }
let community_id = data.community_id; let community_id = data.community_id;
let new_creator = data.person_id; let new_creator = data.user_id;
let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator); let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
if blocking(context.pool(), update).await?.is_err() { if blocking(context.pool(), update).await?.is_err() {
return Err(ApiError::err("couldnt_update_community").into()); return Err(APIError::err("couldnt_update_community").into());
}; };
// You also have to re-do the community_moderator table, reordering it. // You also have to re-do the community_moderator table, reordering it.
@ -818,10 +781,10 @@ impl Perform for TransferCommunity {
.await??; .await??;
let creator_index = community_mods let creator_index = community_mods
.iter() .iter()
.position(|r| r.moderator.id == data.person_id) .position(|r| r.moderator.id == data.user_id)
.context(location_info!())?; .context(location_info!())?;
let creator_person = community_mods.remove(creator_index); let creator_user = community_mods.remove(creator_index);
community_mods.insert(0, creator_person); community_mods.insert(0, creator_user);
let community_id = data.community_id; let community_id = data.community_id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
@ -833,19 +796,19 @@ impl Perform for TransferCommunity {
for cmod in &community_mods { for cmod in &community_mods {
let community_moderator_form = CommunityModeratorForm { let community_moderator_form = CommunityModeratorForm {
community_id: cmod.community.id, community_id: cmod.community.id,
person_id: cmod.moderator.id, user_id: cmod.moderator.id,
}; };
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
if blocking(context.pool(), join).await?.is_err() { if blocking(context.pool(), join).await?.is_err() {
return Err(ApiError::err("community_moderator_already_exists").into()); return Err(APIError::err("community_moderator_already_exists").into());
} }
} }
// Mod tables // Mod tables
let form = ModAddCommunityForm { let form = ModAddCommunityForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
other_person_id: data.person_id, other_user_id: data.user_id,
community_id: data.community_id, community_id: data.community_id,
removed: Some(false), removed: Some(false),
}; };
@ -855,14 +818,14 @@ impl Perform for TransferCommunity {
.await??; .await??;
let community_id = data.community_id; let community_id = data.community_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let community_view = match blocking(context.pool(), move |conn| { let community_view = match blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(person_id)) CommunityView::read(conn, community_id, Some(user_id))
}) })
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}; };
let community_id = data.community_id; let community_id = data.community_id;
@ -872,7 +835,7 @@ impl Perform for TransferCommunity {
.await? .await?
{ {
Ok(moderators) => moderators, Ok(moderators) => moderators,
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}; };
// Return the jwt // Return the jwt
@ -890,7 +853,7 @@ fn send_community_websocket(
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
op: UserOperation, op: UserOperation,
) { ) {
// Strip out the person id and subscribed when sending to others // Strip out the user id and subscribed when sending to others
let mut res_sent = res.clone(); let mut res_sent = res.clone();
res_sent.community_view.subscribed = false; res_sent.community_view.subscribed = false;
@ -901,3 +864,47 @@ fn send_community_websocket(
websocket_id, websocket_id,
}); });
} }
#[async_trait::async_trait(?Send)]
impl Perform for CommunityJoin {
type Response = CommunityJoinResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<CommunityJoinResponse, LemmyError> {
let data: &CommunityJoin = &self;
if let Some(ws_id) = websocket_id {
context.chat_server().do_send(JoinCommunityRoom {
community_id: data.community_id,
id: ws_id,
});
}
Ok(CommunityJoinResponse { joined: true })
}
}
#[async_trait::async_trait(?Send)]
impl Perform for ModJoin {
type Response = ModJoinResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<ModJoinResponse, LemmyError> {
let data: &ModJoin = &self;
if let Some(ws_id) = websocket_id {
context.chat_server().do_send(JoinModRoom {
community_id: data.community_id,
id: ws_id,
});
}
Ok(ModJoinResponse { joined: true })
}
}

View file

@ -1,13 +1,5 @@
use crate::claims::Claims;
use actix_web::{web, web::Data}; use actix_web::{web, web::Data};
use lemmy_api_structs::{
blocking,
comment::*,
community::*,
person::*,
post::*,
site::*,
websocket::*,
};
use lemmy_db_queries::{ use lemmy_db_queries::{
source::{ source::{
community::{CommunityModerator_, Community_}, community::{CommunityModerator_, Community_},
@ -16,41 +8,30 @@ use lemmy_db_queries::{
Crud, Crud,
DbPool, DbPool,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::source::{
source::{
community::{Community, CommunityModerator}, community::{Community, CommunityModerator},
post::Post, post::Post,
site::Site, site::Site,
}, user::User_,
CommunityId,
LocalUserId,
PersonId,
PostId,
}; };
use lemmy_db_views::local_user_view::{LocalUserSettingsView, LocalUserView};
use lemmy_db_views_actor::{ use lemmy_db_views_actor::{
community_person_ban_view::CommunityPersonBanView, community_user_ban_view::CommunityUserBanView,
community_view::CommunityView, community_view::CommunityView,
}; };
use lemmy_utils::{ use lemmy_structs::{blocking, comment::*, community::*, post::*, site::*, user::*};
claims::Claims, use lemmy_utils::{settings::Settings, APIError, ConnectionId, LemmyError};
settings::structs::Settings,
ApiError,
ConnectionId,
LemmyError,
};
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation}; use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
use serde::Deserialize; use serde::Deserialize;
use std::{env, process::Command}; use std::process::Command;
use url::Url; use url::Url;
pub mod claims;
pub mod comment; pub mod comment;
pub mod community; pub mod community;
pub mod local_user;
pub mod post; pub mod post;
pub mod routes;
pub mod site; pub mod site;
pub mod websocket; pub mod user;
pub mod version;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
pub trait Perform { pub trait Perform {
@ -65,121 +46,65 @@ pub trait Perform {
pub(crate) async fn is_mod_or_admin( pub(crate) async fn is_mod_or_admin(
pool: &DbPool, pool: &DbPool,
person_id: PersonId, user_id: i32,
community_id: CommunityId, community_id: i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let is_mod_or_admin = blocking(pool, move |conn| { let is_mod_or_admin = blocking(pool, move |conn| {
CommunityView::is_mod_or_admin(conn, person_id, community_id) CommunityView::is_mod_or_admin(conn, user_id, community_id)
}) })
.await?; .await?;
if !is_mod_or_admin { if !is_mod_or_admin {
return Err(ApiError::err("not_a_mod_or_admin").into()); return Err(APIError::err("not_a_mod_or_admin").into());
}
Ok(())
}
pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> {
let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
if !user.admin {
return Err(APIError::err("not_an_admin").into());
} }
Ok(()) Ok(())
} }
pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> { pub(crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
if !local_user_view.local_user.admin {
return Err(ApiError::err("not_an_admin").into());
}
Ok(())
}
pub(crate) async fn get_post(post_id: PostId, pool: &DbPool) -> Result<Post, LemmyError> {
match blocking(pool, move |conn| Post::read(conn, post_id)).await? { match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
Ok(post) => Ok(post), Ok(post) => Ok(post),
Err(_e) => Err(ApiError::err("couldnt_find_post").into()), Err(_e) => Err(APIError::err("couldnt_find_post").into()),
} }
} }
pub(crate) async fn get_local_user_view_from_jwt( pub(crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result<User_, LemmyError> {
jwt: &str,
pool: &DbPool,
) -> Result<LocalUserView, LemmyError> {
let claims = match Claims::decode(&jwt) { let claims = match Claims::decode(&jwt) {
Ok(claims) => claims.claims, Ok(claims) => claims.claims,
Err(_e) => return Err(ApiError::err("not_logged_in").into()), Err(_e) => return Err(APIError::err("not_logged_in").into()),
}; };
let local_user_id = LocalUserId(claims.sub); let user_id = claims.id;
let local_user_view = let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
// Check for a site ban // Check for a site ban
if local_user_view.person.banned { if user.banned {
return Err(ApiError::err("site_ban").into()); return Err(APIError::err("site_ban").into());
} }
Ok(user)
check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
Ok(local_user_view)
} }
/// Checks if user's token was issued before user's password reset. pub(crate) async fn get_user_from_jwt_opt(
pub(crate) fn check_validator_time(
validator_time: &chrono::NaiveDateTime,
claims: &Claims,
) -> Result<(), LemmyError> {
let user_validation_time = validator_time.timestamp();
if user_validation_time > claims.iat {
Err(ApiError::err("not_logged_in").into())
} else {
Ok(())
}
}
pub(crate) async fn get_local_user_view_from_jwt_opt(
jwt: &Option<String>, jwt: &Option<String>,
pool: &DbPool, pool: &DbPool,
) -> Result<Option<LocalUserView>, LemmyError> { ) -> Result<Option<User_>, LemmyError> {
match jwt { match jwt {
Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool).await?)), Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
None => Ok(None),
}
}
pub(crate) async fn get_local_user_settings_view_from_jwt(
jwt: &str,
pool: &DbPool,
) -> Result<LocalUserSettingsView, LemmyError> {
let claims = match Claims::decode(&jwt) {
Ok(claims) => claims.claims,
Err(_e) => return Err(ApiError::err("not_logged_in").into()),
};
let local_user_id = LocalUserId(claims.sub);
let local_user_view = blocking(pool, move |conn| {
LocalUserSettingsView::read(conn, local_user_id)
})
.await??;
// Check for a site ban
if local_user_view.person.banned {
return Err(ApiError::err("site_ban").into());
}
check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
Ok(local_user_view)
}
pub(crate) async fn get_local_user_settings_view_from_jwt_opt(
jwt: &Option<String>,
pool: &DbPool,
) -> Result<Option<LocalUserSettingsView>, LemmyError> {
match jwt {
Some(jwt) => Ok(Some(
get_local_user_settings_view_from_jwt(jwt, pool).await?,
)),
None => Ok(None), None => Ok(None),
} }
} }
pub(crate) async fn check_community_ban( pub(crate) async fn check_community_ban(
person_id: PersonId, user_id: i32,
community_id: CommunityId, community_id: i32,
pool: &DbPool, pool: &DbPool,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let is_banned = let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
if blocking(pool, is_banned).await? { if blocking(pool, is_banned).await? {
Err(ApiError::err("community_ban").into()) Err(APIError::err("community_ban").into())
} else { } else {
Ok(()) Ok(())
} }
@ -189,7 +114,7 @@ pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result
if score == -1 { if score == -1 {
let site = blocking(pool, move |conn| Site::read_simple(conn)).await??; let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
if !site.enable_downvotes { if !site.enable_downvotes {
return Err(ApiError::err("downvotes_disabled").into()); return Err(APIError::err("downvotes_disabled").into());
} }
} }
Ok(()) Ok(())
@ -199,64 +124,63 @@ pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result
/// or if a community_id is supplied validates the user is a moderator /// or if a community_id is supplied validates the user is a moderator
/// of that community and returns the community id in a vec /// of that community and returns the community id in a vec
/// ///
/// * `person_id` - the person id of the moderator /// * `user_id` - the user id of the moderator
/// * `community_id` - optional community id to check for moderator privileges /// * `community_id` - optional community id to check for moderator privileges
/// * `pool` - the diesel db pool /// * `pool` - the diesel db pool
pub(crate) async fn collect_moderated_communities( pub(crate) async fn collect_moderated_communities(
person_id: PersonId, user_id: i32,
community_id: Option<CommunityId>, community_id: Option<i32>,
pool: &DbPool, pool: &DbPool,
) -> Result<Vec<CommunityId>, LemmyError> { ) -> Result<Vec<i32>, LemmyError> {
if let Some(community_id) = community_id { if let Some(community_id) = community_id {
// if the user provides a community_id, just check for mod/admin privileges // if the user provides a community_id, just check for mod/admin privileges
is_mod_or_admin(pool, person_id, community_id).await?; is_mod_or_admin(pool, user_id, community_id).await?;
Ok(vec![community_id]) Ok(vec![community_id])
} else { } else {
let ids = blocking(pool, move |conn: &'_ _| { let ids = blocking(pool, move |conn: &'_ _| {
CommunityModerator::get_person_moderated_communities(conn, person_id) CommunityModerator::get_user_moderated_communities(conn, user_id)
}) })
.await??; .await??;
Ok(ids) Ok(ids)
} }
} }
pub(crate) async fn build_federated_instances( pub(crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
pool: &DbPool, if let Some(Some(item)) = &item {
) -> Result<Option<FederatedInstances>, LemmyError> { if Url::parse(item).is_err() {
if Settings::get().federation().enabled { return Err(APIError::err("invalid_url").into());
}
}
Ok(())
}
pub(crate) async fn linked_instances(pool: &DbPool) -> Result<Vec<String>, LemmyError> {
let mut instances: Vec<String> = Vec::new();
if Settings::get().federation.enabled {
let distinct_communities = blocking(pool, move |conn| { let distinct_communities = blocking(pool, move |conn| {
Community::distinct_federated_communities(conn) Community::distinct_federated_communities(conn)
}) })
.await??; .await??;
let allowed = Settings::get().get_allowed_instances(); instances = distinct_communities
let blocked = Settings::get().get_blocked_instances();
let mut linked = distinct_communities
.iter() .iter()
.map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string())) .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
.collect::<Result<Vec<String>, LemmyError>>()?; .collect::<Result<Vec<String>, LemmyError>>()?;
if let Some(allowed) = allowed.as_ref() { instances.append(&mut Settings::get().get_allowed_instances());
linked.extend_from_slice(allowed); instances.retain(|a| {
} !Settings::get().get_blocked_instances().contains(a)
&& !a.eq("")
if let Some(blocked) = blocked.as_ref() { && !a.eq(&Settings::get().hostname)
linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname())); });
}
// Sort and remove dupes // Sort and remove dupes
linked.sort_unstable(); instances.sort_unstable();
linked.dedup(); instances.dedup();
Ok(Some(FederatedInstances {
linked,
allowed,
blocked,
}))
} else {
Ok(None)
} }
Ok(instances)
} }
pub async fn match_websocket_operation( pub async fn match_websocket_operation(
@ -270,17 +194,17 @@ pub async fn match_websocket_operation(
UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await, UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await, UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await, UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
UserOperation::GetPersonDetails => { UserOperation::GetUserDetails => {
do_websocket_operation::<GetPersonDetails>(context, id, op, data).await do_websocket_operation::<GetUserDetails>(context, id, op, data).await
} }
UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await, UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await, UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await, UserOperation::BanUser => do_websocket_operation::<BanUser>(context, id, op, data).await,
UserOperation::GetPersonMentions => { UserOperation::GetUserMentions => {
do_websocket_operation::<GetPersonMentions>(context, id, op, data).await do_websocket_operation::<GetUserMentions>(context, id, op, data).await
} }
UserOperation::MarkPersonMentionAsRead => { UserOperation::MarkUserMentionAsRead => {
do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await do_websocket_operation::<MarkUserMentionAsRead>(context, id, op, data).await
} }
UserOperation::MarkAllAsRead => { UserOperation::MarkAllAsRead => {
do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
@ -342,6 +266,9 @@ pub async fn match_websocket_operation(
UserOperation::TransferSite => { UserOperation::TransferSite => {
do_websocket_operation::<TransferSite>(context, id, op, data).await do_websocket_operation::<TransferSite>(context, id, op, data).await
} }
UserOperation::ListCategories => {
do_websocket_operation::<ListCategories>(context, id, op, data).await
}
// Community ops // Community ops
UserOperation::GetCommunity => { UserOperation::GetCommunity => {
@ -478,11 +405,7 @@ pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyEr
pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> { pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
// Make a temp file path // Make a temp file path
let uuid = uuid::Uuid::new_v4().to_string(); let uuid = uuid::Uuid::new_v4().to_string();
let file_path = format!( let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
"{}/lemmy_espeak_{}.wav",
env::temp_dir().to_string_lossy(),
&uuid
);
// Write the wav file // Write the wav file
Command::new("espeak") Command::new("espeak")
@ -503,81 +426,9 @@ pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
Ok(base64) Ok(base64)
} }
/// Checks the password length
pub(crate) fn password_length_check(pass: &str) -> Result<(), LemmyError> {
if pass.len() > 60 {
Err(ApiError::err("invalid_password").into())
} else {
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{captcha_espeak_wav_base64, check_validator_time}; use crate::captcha_espeak_wav_base64;
use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud};
use lemmy_db_schema::source::{
local_user::{LocalUser, LocalUserForm},
person::{Person, PersonForm},
};
use lemmy_utils::claims::Claims;
#[test]
fn test_should_not_validate_user_token_after_password_change() {
let conn = establish_unpooled_connection();
let new_person = PersonForm {
name: "Gerry9812".into(),
preferred_username: None,
avatar: None,
banner: None,
banned: None,
deleted: None,
published: None,
updated: None,
actor_id: None,
bio: None,
local: None,
private_key: None,
public_key: None,
last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
};
let inserted_person = Person::create(&conn, &new_person).unwrap();
let local_user_form = LocalUserForm {
person_id: inserted_person.id,
email: None,
matrix_user_id: None,
password_encrypted: "123456".to_string(),
admin: None,
show_nsfw: None,
theme: None,
default_sort_type: None,
default_listing_type: None,
lang: None,
show_avatars: None,
send_notifications_to_email: None,
};
let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap();
let jwt = Claims::jwt(inserted_local_user.id.0).unwrap();
let claims = Claims::decode(&jwt).unwrap().claims;
let check = check_validator_time(&inserted_local_user.validator_time, &claims);
assert!(check.is_ok());
// The check should fail, since the validator time is now newer than the jwt issue time
let updated_local_user =
LocalUser::update_password(&conn, inserted_local_user.id, &"password111").unwrap();
let check_after = check_validator_time(&updated_local_user.validator_time, &claims);
assert!(check_after.is_err());
let num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
assert_eq!(1, num_deleted);
}
#[test] #[test]
fn test_espeak() { fn test_espeak() {

View file

@ -1,15 +1,15 @@
use crate::{ use crate::{
check_community_ban, check_community_ban,
check_downvotes_enabled, check_downvotes_enabled,
check_optional_url,
collect_moderated_communities, collect_moderated_communities,
get_local_user_view_from_jwt, get_user_from_jwt,
get_local_user_view_from_jwt_opt, get_user_from_jwt_opt,
is_mod_or_admin, is_mod_or_admin,
Perform, Perform,
}; };
use actix_web::web::Data; use actix_web::web::Data;
use lemmy_api_structs::{blocking, post::*}; use lemmy_apub::{ApubLikeableType, ApubObjectType};
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
use lemmy_db_queries::{ use lemmy_db_queries::{
source::post::Post_, source::post::Post_,
Crud, Crud,
@ -36,15 +36,17 @@ use lemmy_db_views_actor::{
community_moderator_view::CommunityModeratorView, community_moderator_view::CommunityModeratorView,
community_view::CommunityView, community_view::CommunityView,
}; };
use lemmy_structs::{blocking, post::*};
use lemmy_utils::{ use lemmy_utils::{
apub::{make_apub_endpoint, EndpointType},
request::fetch_iframely_and_pictrs_data, request::fetch_iframely_and_pictrs_data,
utils::{check_slurs, check_slurs_opt, is_valid_post_title}, utils::{check_slurs, check_slurs_opt, is_valid_post_title},
ApiError, APIError,
ConnectionId, ConnectionId,
LemmyError, LemmyError,
}; };
use lemmy_websocket::{ use lemmy_websocket::{
messages::{GetPostUsersOnline, SendModRoomMessage, SendPost, SendUserRoomMessage}, messages::{GetPostUsersOnline, JoinPostRoom, SendModRoomMessage, SendPost, SendUserRoomMessage},
LemmyContext, LemmyContext,
UserOperation, UserOperation,
}; };
@ -60,28 +62,29 @@ impl Perform for CreatePost {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> { ) -> Result<PostResponse, LemmyError> {
let data: &CreatePost = &self; let data: &CreatePost = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
check_slurs(&data.name)?; check_slurs(&data.name)?;
check_slurs_opt(&data.body)?; check_slurs_opt(&data.body)?;
if !is_valid_post_title(&data.name) { if !is_valid_post_title(&data.name) {
return Err(ApiError::err("invalid_post_title").into()); return Err(APIError::err("invalid_post_title").into());
} }
check_community_ban(local_user_view.person.id, data.community_id, context.pool()).await?; check_community_ban(user.id, data.community_id, context.pool()).await?;
check_optional_url(&Some(data.url.to_owned()))?;
// Fetch Iframely and pictrs cached image // Fetch Iframely and pictrs cached image
let data_url = data.url.as_ref();
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
fetch_iframely_and_pictrs_data(context.client(), data_url).await; fetch_iframely_and_pictrs_data(context.client(), data.url.to_owned()).await;
let post_form = PostForm { let post_form = PostForm {
name: data.name.trim().to_owned(), name: data.name.trim().to_owned(),
url: data_url.map(|u| u.to_owned().into()), url: data.url.to_owned(),
body: data.body.to_owned(), body: data.body.to_owned(),
community_id: data.community_id, community_id: data.community_id,
creator_id: local_user_view.person.id, creator_id: user.id,
removed: None, removed: None,
deleted: None, deleted: None,
nsfw: data.nsfw, nsfw: data.nsfw,
@ -91,7 +94,7 @@ impl Perform for CreatePost {
embed_title: iframely_title, embed_title: iframely_title,
embed_description: iframely_description, embed_description: iframely_description,
embed_html: iframely_html, embed_html: iframely_html,
thumbnail_url: pictrs_thumbnail.map(|u| u.into()), thumbnail_url: pictrs_thumbnail,
ap_id: None, ap_id: None,
local: true, local: true,
published: None, published: None,
@ -107,50 +110,47 @@ impl Perform for CreatePost {
"couldnt_create_post" "couldnt_create_post"
}; };
return Err(ApiError::err(err_type).into()); return Err(APIError::err(err_type).into());
} }
}; };
let inserted_post_id = inserted_post.id; let inserted_post_id = inserted_post.id;
let updated_post = match blocking(context.pool(), move |conn| -> Result<Post, LemmyError> { let updated_post = match blocking(context.pool(), move |conn| {
let apub_id = generate_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string())?; let apub_id =
Ok(Post::update_ap_id(conn, inserted_post_id, apub_id)?) make_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string()).to_string();
Post::update_ap_id(conn, inserted_post_id, apub_id)
}) })
.await? .await?
{ {
Ok(post) => post, Ok(post) => post,
Err(_e) => return Err(ApiError::err("couldnt_create_post").into()), Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
}; };
updated_post updated_post.send_create(&user, context).await?;
.send_create(&local_user_view.person, context)
.await?;
// They like their own post by default // They like their own post by default
let like_form = PostLikeForm { let like_form = PostLikeForm {
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: local_user_view.person.id, user_id: user.id,
score: 1, score: 1,
}; };
let like = move |conn: &'_ _| PostLike::like(conn, &like_form); let like = move |conn: &'_ _| PostLike::like(conn, &like_form);
if blocking(context.pool(), like).await?.is_err() { if blocking(context.pool(), like).await?.is_err() {
return Err(ApiError::err("couldnt_like_post").into()); return Err(APIError::err("couldnt_like_post").into());
} }
updated_post updated_post.send_like(&user, context).await?;
.send_like(&local_user_view.person, context)
.await?;
// Refetch the view // Refetch the view
let inserted_post_id = inserted_post.id; let inserted_post_id = inserted_post.id;
let post_view = match blocking(context.pool(), move |conn| { let post_view = match blocking(context.pool(), move |conn| {
PostView::read(conn, inserted_post_id, Some(local_user_view.person.id)) PostView::read(conn, inserted_post_id, Some(user.id))
}) })
.await? .await?
{ {
Ok(post) => post, Ok(post) => post,
Err(_e) => return Err(ApiError::err("couldnt_find_post").into()), Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
}; };
let res = PostResponse { post_view }; let res = PostResponse { post_view };
@ -175,23 +175,23 @@ impl Perform for GetPost {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetPostResponse, LemmyError> { ) -> Result<GetPostResponse, LemmyError> {
let data: &GetPost = &self; let data: &GetPost = &self;
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
let person_id = local_user_view.map(|u| u.person.id); let user_id = user.map(|u| u.id);
let id = data.id; let id = data.id;
let post_view = match blocking(context.pool(), move |conn| { let post_view = match blocking(context.pool(), move |conn| {
PostView::read(conn, id, person_id) PostView::read(conn, id, user_id)
}) })
.await? .await?
{ {
Ok(post) => post, Ok(post) => post,
Err(_e) => return Err(ApiError::err("couldnt_find_post").into()), Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
}; };
let id = data.id; let id = data.id;
let comments = blocking(context.pool(), move |conn| { let comments = blocking(context.pool(), move |conn| {
CommentQueryBuilder::create(conn) CommentQueryBuilder::create(conn)
.my_person_id(person_id) .my_user_id(user_id)
.post_id(id) .post_id(id)
.limit(9999) .limit(9999)
.list() .list()
@ -206,12 +206,12 @@ impl Perform for GetPost {
// Necessary for the sidebar // Necessary for the sidebar
let community_view = match blocking(context.pool(), move |conn| { let community_view = match blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, person_id) CommunityView::read(conn, community_id, user_id)
}) })
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}; };
let online = context let online = context
@ -241,15 +241,15 @@ impl Perform for GetPosts {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetPostsResponse, LemmyError> { ) -> Result<GetPostsResponse, LemmyError> {
let data: &GetPosts = &self; let data: &GetPosts = &self;
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
let person_id = match &local_user_view { let user_id = match &user {
Some(uv) => Some(uv.person.id), Some(user) => Some(user.id),
None => None, None => None,
}; };
let show_nsfw = match &local_user_view { let show_nsfw = match &user {
Some(uv) => uv.local_user.show_nsfw, Some(user) => user.show_nsfw,
None => false, None => false,
}; };
@ -267,7 +267,7 @@ impl Perform for GetPosts {
.show_nsfw(show_nsfw) .show_nsfw(show_nsfw)
.community_id(community_id) .community_id(community_id)
.community_name(community_name) .community_name(community_name)
.my_person_id(person_id) .my_user_id(user_id)
.page(page) .page(page)
.limit(limit) .limit(limit)
.list() .list()
@ -275,7 +275,7 @@ impl Perform for GetPosts {
.await? .await?
{ {
Ok(posts) => posts, Ok(posts) => posts,
Err(_e) => return Err(ApiError::err("couldnt_get_posts").into()), Err(_e) => return Err(APIError::err("couldnt_get_posts").into()),
}; };
Ok(GetPostsResponse { posts }) Ok(GetPostsResponse { posts })
@ -292,7 +292,7 @@ impl Perform for CreatePostLike {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> { ) -> Result<PostResponse, LemmyError> {
let data: &CreatePostLike = &self; let data: &CreatePostLike = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Don't do a downvote if site has downvotes disabled // Don't do a downvote if site has downvotes disabled
check_downvotes_enabled(data.score, context.pool()).await?; check_downvotes_enabled(data.score, context.pool()).await?;
@ -301,18 +301,18 @@ impl Perform for CreatePostLike {
let post_id = data.post_id; let post_id = data.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?; check_community_ban(user.id, post.community_id, context.pool()).await?;
let like_form = PostLikeForm { let like_form = PostLikeForm {
post_id: data.post_id, post_id: data.post_id,
person_id: local_user_view.person.id, user_id: user.id,
score: data.score, score: data.score,
}; };
// Remove any likes first // Remove any likes first
let person_id = local_user_view.person.id; let user_id = user.id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
PostLike::remove(conn, person_id, post_id) PostLike::remove(conn, user_id, post_id)
}) })
.await??; .await??;
@ -322,29 +322,27 @@ impl Perform for CreatePostLike {
let like_form2 = like_form.clone(); let like_form2 = like_form.clone();
let like = move |conn: &'_ _| PostLike::like(conn, &like_form2); let like = move |conn: &'_ _| PostLike::like(conn, &like_form2);
if blocking(context.pool(), like).await?.is_err() { if blocking(context.pool(), like).await?.is_err() {
return Err(ApiError::err("couldnt_like_post").into()); return Err(APIError::err("couldnt_like_post").into());
} }
if like_form.score == 1 { if like_form.score == 1 {
post.send_like(&local_user_view.person, context).await?; post.send_like(&user, context).await?;
} else if like_form.score == -1 { } else if like_form.score == -1 {
post.send_dislike(&local_user_view.person, context).await?; post.send_dislike(&user, context).await?;
} }
} else { } else {
post post.send_undo_like(&user, context).await?;
.send_undo_like(&local_user_view.person, context)
.await?;
} }
let post_id = data.post_id; let post_id = data.post_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let post_view = match blocking(context.pool(), move |conn| { let post_view = match blocking(context.pool(), move |conn| {
PostView::read(conn, post_id, Some(person_id)) PostView::read(conn, post_id, Some(user_id))
}) })
.await? .await?
{ {
Ok(post) => post, Ok(post) => post,
Err(_e) => return Err(ApiError::err("couldnt_find_post").into()), Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
}; };
let res = PostResponse { post_view }; let res = PostResponse { post_view };
@ -369,38 +367,32 @@ impl Perform for EditPost {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> { ) -> Result<PostResponse, LemmyError> {
let data: &EditPost = &self; let data: &EditPost = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
check_slurs(&data.name)?; check_slurs(&data.name)?;
check_slurs_opt(&data.body)?; check_slurs_opt(&data.body)?;
if !is_valid_post_title(&data.name) { if !is_valid_post_title(&data.name) {
return Err(ApiError::err("invalid_post_title").into()); return Err(APIError::err("invalid_post_title").into());
} }
let post_id = data.post_id; let edit_id = data.edit_id;
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
check_community_ban( check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
local_user_view.person.id,
orig_post.community_id,
context.pool(),
)
.await?;
// Verify that only the creator can edit // Verify that only the creator can edit
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) { if !Post::is_post_creator(user.id, orig_post.creator_id) {
return Err(ApiError::err("no_post_edit_allowed").into()); return Err(APIError::err("no_post_edit_allowed").into());
} }
// Fetch Iframely and Pictrs cached image // Fetch Iframely and Pictrs cached image
let data_url = data.url.as_ref();
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
fetch_iframely_and_pictrs_data(context.client(), data_url).await; fetch_iframely_and_pictrs_data(context.client(), data.url.to_owned()).await;
let post_form = PostForm { let post_form = PostForm {
name: data.name.trim().to_owned(), name: data.name.trim().to_owned(),
url: data_url.map(|u| u.to_owned().into()), url: data.url.to_owned(),
body: data.body.to_owned(), body: data.body.to_owned(),
nsfw: data.nsfw, nsfw: data.nsfw,
creator_id: orig_post.creator_id.to_owned(), creator_id: orig_post.creator_id.to_owned(),
@ -413,15 +405,15 @@ impl Perform for EditPost {
embed_title: iframely_title, embed_title: iframely_title,
embed_description: iframely_description, embed_description: iframely_description,
embed_html: iframely_html, embed_html: iframely_html,
thumbnail_url: pictrs_thumbnail.map(|u| u.into()), thumbnail_url: pictrs_thumbnail,
ap_id: Some(orig_post.ap_id), ap_id: Some(orig_post.ap_id),
local: orig_post.local, local: orig_post.local,
published: None, published: None,
}; };
let post_id = data.post_id; let edit_id = data.edit_id;
let res = blocking(context.pool(), move |conn| { let res = blocking(context.pool(), move |conn| {
Post::update(conn, post_id, &post_form) Post::update(conn, edit_id, &post_form)
}) })
.await?; .await?;
let updated_post: Post = match res { let updated_post: Post = match res {
@ -433,18 +425,16 @@ impl Perform for EditPost {
"couldnt_update_post" "couldnt_update_post"
}; };
return Err(ApiError::err(err_type).into()); return Err(APIError::err(err_type).into());
} }
}; };
// Send apub update // Send apub update
updated_post updated_post.send_update(&user, context).await?;
.send_update(&local_user_view.person, context)
.await?;
let post_id = data.post_id; let edit_id = data.edit_id;
let post_view = blocking(context.pool(), move |conn| { let post_view = blocking(context.pool(), move |conn| {
PostView::read(conn, post_id, Some(local_user_view.person.id)) PostView::read(conn, edit_id, Some(user.id))
}) })
.await??; .await??;
@ -470,46 +460,37 @@ impl Perform for DeletePost {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> { ) -> Result<PostResponse, LemmyError> {
let data: &DeletePost = &self; let data: &DeletePost = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let post_id = data.post_id; let edit_id = data.edit_id;
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
check_community_ban( check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
local_user_view.person.id,
orig_post.community_id,
context.pool(),
)
.await?;
// Verify that only the creator can delete // Verify that only the creator can delete
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) { if !Post::is_post_creator(user.id, orig_post.creator_id) {
return Err(ApiError::err("no_post_edit_allowed").into()); return Err(APIError::err("no_post_edit_allowed").into());
} }
// Update the post // Update the post
let post_id = data.post_id; let edit_id = data.edit_id;
let deleted = data.deleted; let deleted = data.deleted;
let updated_post = blocking(context.pool(), move |conn| { let updated_post = blocking(context.pool(), move |conn| {
Post::update_deleted(conn, post_id, deleted) Post::update_deleted(conn, edit_id, deleted)
}) })
.await??; .await??;
// apub updates // apub updates
if deleted { if deleted {
updated_post updated_post.send_delete(&user, context).await?;
.send_delete(&local_user_view.person, context)
.await?;
} else { } else {
updated_post updated_post.send_undo_delete(&user, context).await?;
.send_undo_delete(&local_user_view.person, context)
.await?;
} }
// Refetch the post // Refetch the post
let post_id = data.post_id; let edit_id = data.edit_id;
let post_view = blocking(context.pool(), move |conn| { let post_view = blocking(context.pool(), move |conn| {
PostView::read(conn, post_id, Some(local_user_view.person.id)) PostView::read(conn, edit_id, Some(user.id))
}) })
.await??; .await??;
@ -535,38 +516,28 @@ impl Perform for RemovePost {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> { ) -> Result<PostResponse, LemmyError> {
let data: &RemovePost = &self; let data: &RemovePost = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let post_id = data.post_id; let edit_id = data.edit_id;
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
check_community_ban( check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
local_user_view.person.id,
orig_post.community_id,
context.pool(),
)
.await?;
// Verify that only the mods can remove // Verify that only the mods can remove
is_mod_or_admin( is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
context.pool(),
local_user_view.person.id,
orig_post.community_id,
)
.await?;
// Update the post // Update the post
let post_id = data.post_id; let edit_id = data.edit_id;
let removed = data.removed; let removed = data.removed;
let updated_post = blocking(context.pool(), move |conn| { let updated_post = blocking(context.pool(), move |conn| {
Post::update_removed(conn, post_id, removed) Post::update_removed(conn, edit_id, removed)
}) })
.await??; .await??;
// Mod tables // Mod tables
let form = ModRemovePostForm { let form = ModRemovePostForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
post_id: data.post_id, post_id: data.edit_id,
removed: Some(removed), removed: Some(removed),
reason: data.reason.to_owned(), reason: data.reason.to_owned(),
}; };
@ -577,20 +548,16 @@ impl Perform for RemovePost {
// apub updates // apub updates
if removed { if removed {
updated_post updated_post.send_remove(&user, context).await?;
.send_remove(&local_user_view.person, context)
.await?;
} else { } else {
updated_post updated_post.send_undo_remove(&user, context).await?;
.send_undo_remove(&local_user_view.person, context)
.await?;
} }
// Refetch the post // Refetch the post
let post_id = data.post_id; let edit_id = data.edit_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let post_view = blocking(context.pool(), move |conn| { let post_view = blocking(context.pool(), move |conn| {
PostView::read(conn, post_id, Some(person_id)) PostView::read(conn, edit_id, Some(user_id))
}) })
.await??; .await??;
@ -616,51 +583,39 @@ impl Perform for LockPost {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> { ) -> Result<PostResponse, LemmyError> {
let data: &LockPost = &self; let data: &LockPost = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let post_id = data.post_id; let edit_id = data.edit_id;
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
check_community_ban( check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
local_user_view.person.id,
orig_post.community_id,
context.pool(),
)
.await?;
// Verify that only the mods can lock // Verify that only the mods can lock
is_mod_or_admin( is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
context.pool(),
local_user_view.person.id,
orig_post.community_id,
)
.await?;
// Update the post // Update the post
let post_id = data.post_id; let edit_id = data.edit_id;
let locked = data.locked; let locked = data.locked;
let updated_post = blocking(context.pool(), move |conn| { let updated_post = blocking(context.pool(), move |conn| {
Post::update_locked(conn, post_id, locked) Post::update_locked(conn, edit_id, locked)
}) })
.await??; .await??;
// Mod tables // Mod tables
let form = ModLockPostForm { let form = ModLockPostForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
post_id: data.post_id, post_id: data.edit_id,
locked: Some(locked), locked: Some(locked),
}; };
blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??; blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
// apub updates // apub updates
updated_post updated_post.send_update(&user, context).await?;
.send_update(&local_user_view.person, context)
.await?;
// Refetch the post // Refetch the post
let post_id = data.post_id; let edit_id = data.edit_id;
let post_view = blocking(context.pool(), move |conn| { let post_view = blocking(context.pool(), move |conn| {
PostView::read(conn, post_id, Some(local_user_view.person.id)) PostView::read(conn, edit_id, Some(user.id))
}) })
.await??; .await??;
@ -686,38 +641,28 @@ impl Perform for StickyPost {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> { ) -> Result<PostResponse, LemmyError> {
let data: &StickyPost = &self; let data: &StickyPost = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let post_id = data.post_id; let edit_id = data.edit_id;
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let orig_post = blocking(context.pool(), move |conn| Post::read(conn, edit_id)).await??;
check_community_ban( check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
local_user_view.person.id,
orig_post.community_id,
context.pool(),
)
.await?;
// Verify that only the mods can sticky // Verify that only the mods can sticky
is_mod_or_admin( is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
context.pool(),
local_user_view.person.id,
orig_post.community_id,
)
.await?;
// Update the post // Update the post
let post_id = data.post_id; let edit_id = data.edit_id;
let stickied = data.stickied; let stickied = data.stickied;
let updated_post = blocking(context.pool(), move |conn| { let updated_post = blocking(context.pool(), move |conn| {
Post::update_stickied(conn, post_id, stickied) Post::update_stickied(conn, edit_id, stickied)
}) })
.await??; .await??;
// Mod tables // Mod tables
let form = ModStickyPostForm { let form = ModStickyPostForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
post_id: data.post_id, post_id: data.edit_id,
stickied: Some(stickied), stickied: Some(stickied),
}; };
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
@ -727,14 +672,12 @@ impl Perform for StickyPost {
// Apub updates // Apub updates
// TODO stickied should pry work like locked for ease of use // TODO stickied should pry work like locked for ease of use
updated_post updated_post.send_update(&user, context).await?;
.send_update(&local_user_view.person, context)
.await?;
// Refetch the post // Refetch the post
let post_id = data.post_id; let edit_id = data.edit_id;
let post_view = blocking(context.pool(), move |conn| { let post_view = blocking(context.pool(), move |conn| {
PostView::read(conn, post_id, Some(local_user_view.person.id)) PostView::read(conn, edit_id, Some(user.id))
}) })
.await??; .await??;
@ -760,29 +703,29 @@ impl Perform for SavePost {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> { ) -> Result<PostResponse, LemmyError> {
let data: &SavePost = &self; let data: &SavePost = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let post_saved_form = PostSavedForm { let post_saved_form = PostSavedForm {
post_id: data.post_id, post_id: data.post_id,
person_id: local_user_view.person.id, user_id: user.id,
}; };
if data.save { if data.save {
let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form); let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form);
if blocking(context.pool(), save).await?.is_err() { if blocking(context.pool(), save).await?.is_err() {
return Err(ApiError::err("couldnt_save_post").into()); return Err(APIError::err("couldnt_save_post").into());
} }
} else { } else {
let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form); let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form);
if blocking(context.pool(), unsave).await?.is_err() { if blocking(context.pool(), unsave).await?.is_err() {
return Err(ApiError::err("couldnt_save_post").into()); return Err(APIError::err("couldnt_save_post").into());
} }
} }
let post_id = data.post_id; let post_id = data.post_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let post_view = blocking(context.pool(), move |conn| { let post_view = blocking(context.pool(), move |conn| {
PostView::read(conn, post_id, Some(person_id)) PostView::read(conn, post_id, Some(user_id))
}) })
.await??; .await??;
@ -790,6 +733,28 @@ impl Perform for SavePost {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for PostJoin {
type Response = PostJoinResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<PostJoinResponse, LemmyError> {
let data: &PostJoin = &self;
if let Some(ws_id) = websocket_id {
context.chat_server().do_send(JoinPostRoom {
post_id: data.post_id,
id: ws_id,
});
}
Ok(PostJoinResponse { joined: true })
}
}
/// Creates a post report and notifies the moderators of the community /// Creates a post report and notifies the moderators of the community
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl Perform for CreatePostReport { impl Perform for CreatePostReport {
@ -801,28 +766,28 @@ impl Perform for CreatePostReport {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CreatePostReportResponse, LemmyError> { ) -> Result<CreatePostReportResponse, LemmyError> {
let data: &CreatePostReport = &self; let data: &CreatePostReport = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// check size of report and check for whitespace // check size of report and check for whitespace
let reason = data.reason.trim(); let reason = data.reason.trim();
if reason.is_empty() { if reason.is_empty() {
return Err(ApiError::err("report_reason_required").into()); return Err(APIError::err("report_reason_required").into());
} }
if reason.chars().count() > 1000 { if reason.len() > 1000 {
return Err(ApiError::err("report_too_long").into()); return Err(APIError::err("report_too_long").into());
} }
let person_id = local_user_view.person.id; let user_id = user.id;
let post_id = data.post_id; let post_id = data.post_id;
let post_view = blocking(context.pool(), move |conn| { let post_view = blocking(context.pool(), move |conn| {
PostView::read(&conn, post_id, None) PostView::read(&conn, post_id, None)
}) })
.await??; .await??;
check_community_ban(person_id, post_view.community.id, context.pool()).await?; check_community_ban(user_id, post_view.community.id, context.pool()).await?;
let report_form = PostReportForm { let report_form = PostReportForm {
creator_id: person_id, creator_id: user_id,
post_id, post_id,
original_post_name: post_view.post.name, original_post_name: post_view.post.name,
original_post_url: post_view.post.url, original_post_url: post_view.post.url,
@ -836,7 +801,7 @@ impl Perform for CreatePostReport {
.await? .await?
{ {
Ok(report) => report, Ok(report) => report,
Err(_e) => return Err(ApiError::err("couldnt_create_report").into()), Err(_e) => return Err(APIError::err("couldnt_create_report").into()),
}; };
let res = CreatePostReportResponse { success: true }; let res = CreatePostReportResponse { success: true };
@ -844,7 +809,7 @@ impl Perform for CreatePostReport {
context.chat_server().do_send(SendUserRoomMessage { context.chat_server().do_send(SendUserRoomMessage {
op: UserOperation::CreatePostReport, op: UserOperation::CreatePostReport,
response: res.clone(), response: res.clone(),
local_recipient_id: local_user_view.local_user.id, recipient_id: user.id,
websocket_id, websocket_id,
}); });
@ -870,7 +835,7 @@ impl Perform for ResolvePostReport {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<ResolvePostReportResponse, LemmyError> { ) -> Result<ResolvePostReportResponse, LemmyError> {
let data: &ResolvePostReport = &self; let data: &ResolvePostReport = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let report_id = data.report_id; let report_id = data.report_id;
let report = blocking(context.pool(), move |conn| { let report = blocking(context.pool(), move |conn| {
@ -878,15 +843,15 @@ impl Perform for ResolvePostReport {
}) })
.await??; .await??;
let person_id = local_user_view.person.id; let user_id = user.id;
is_mod_or_admin(context.pool(), person_id, report.community.id).await?; is_mod_or_admin(context.pool(), user_id, report.community.id).await?;
let resolved = data.resolved; let resolved = data.resolved;
let resolve_fun = move |conn: &'_ _| { let resolve_fun = move |conn: &'_ _| {
if resolved { if resolved {
PostReport::resolve(conn, report_id, person_id) PostReport::resolve(conn, report_id, user_id)
} else { } else {
PostReport::unresolve(conn, report_id, person_id) PostReport::unresolve(conn, report_id, user_id)
} }
}; };
@ -896,7 +861,7 @@ impl Perform for ResolvePostReport {
}; };
if blocking(context.pool(), resolve_fun).await?.is_err() { if blocking(context.pool(), resolve_fun).await?.is_err() {
return Err(ApiError::err("couldnt_resolve_report").into()); return Err(APIError::err("couldnt_resolve_report").into());
}; };
context.chat_server().do_send(SendModRoomMessage { context.chat_server().do_send(SendModRoomMessage {
@ -922,12 +887,12 @@ impl Perform for ListPostReports {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<ListPostReportsResponse, LemmyError> { ) -> Result<ListPostReportsResponse, LemmyError> {
let data: &ListPostReports = &self; let data: &ListPostReports = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let person_id = local_user_view.person.id; let user_id = user.id;
let community_id = data.community; let community_id = data.community;
let community_ids = let community_ids =
collect_moderated_communities(person_id, community_id, context.pool()).await?; collect_moderated_communities(user_id, community_id, context.pool()).await?;
let page = data.page; let page = data.page;
let limit = data.limit; let limit = data.limit;
@ -945,7 +910,7 @@ impl Perform for ListPostReports {
context.chat_server().do_send(SendUserRoomMessage { context.chat_server().do_send(SendUserRoomMessage {
op: UserOperation::ListPostReports, op: UserOperation::ListPostReports,
response: res.clone(), response: res.clone(),
local_recipient_id: local_user_view.local_user.id, recipient_id: user.id,
websocket_id, websocket_id,
}); });

View file

@ -1,19 +1,17 @@
use crate::{ use crate::{
build_federated_instances, get_user_from_jwt,
get_local_user_settings_view_from_jwt, get_user_from_jwt_opt,
get_local_user_settings_view_from_jwt_opt,
get_local_user_view_from_jwt,
get_local_user_view_from_jwt_opt,
is_admin, is_admin,
linked_instances,
version,
Perform, Perform,
}; };
use actix_web::web::Data; use actix_web::web::Data;
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::{blocking, person::Register, site::*};
use lemmy_apub::fetcher::search::search_by_apub_id; use lemmy_apub::fetcher::search::search_by_apub_id;
use lemmy_db_queries::{ use lemmy_db_queries::{
diesel_option_overwrite_to_url, diesel_option_overwrite,
source::site::Site_, source::{category::Category_, site::Site_},
Crud, Crud,
SearchType, SearchType,
SortType, SortType,
@ -21,6 +19,7 @@ use lemmy_db_queries::{
use lemmy_db_schema::{ use lemmy_db_schema::{
naive_now, naive_now,
source::{ source::{
category::Category,
moderator::*, moderator::*,
site::{Site, *}, site::{Site, *},
}, },
@ -32,7 +31,7 @@ use lemmy_db_views::{
}; };
use lemmy_db_views_actor::{ use lemmy_db_views_actor::{
community_view::CommunityQueryBuilder, community_view::CommunityQueryBuilder,
person_view::{PersonQueryBuilder, PersonViewSafe}, user_view::{UserQueryBuilder, UserViewSafe},
}; };
use lemmy_db_views_moderator::{ use lemmy_db_views_moderator::{
mod_add_community_view::ModAddCommunityView, mod_add_community_view::ModAddCommunityView,
@ -45,12 +44,12 @@ use lemmy_db_views_moderator::{
mod_remove_post_view::ModRemovePostView, mod_remove_post_view::ModRemovePostView,
mod_sticky_post_view::ModStickyPostView, mod_sticky_post_view::ModStickyPostView,
}; };
use lemmy_structs::{blocking, site::*, user::Register};
use lemmy_utils::{ use lemmy_utils::{
location_info, location_info,
settings::structs::Settings, settings::Settings,
utils::{check_slurs, check_slurs_opt}, utils::{check_slurs, check_slurs_opt},
version, APIError,
ApiError,
ConnectionId, ConnectionId,
LemmyError, LemmyError,
}; };
@ -62,6 +61,24 @@ use lemmy_websocket::{
use log::{debug, info}; use log::{debug, info};
use std::str::FromStr; use std::str::FromStr;
#[async_trait::async_trait(?Send)]
impl Perform for ListCategories {
type Response = ListCategoriesResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<ListCategoriesResponse, LemmyError> {
let _data: &ListCategories = &self;
let categories = blocking(context.pool(), move |conn| Category::list_all(conn)).await??;
// Return the jwt
Ok(ListCategoriesResponse { categories })
}
}
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl Perform for GetModlog { impl Perform for GetModlog {
type Response = GetModlogResponse; type Response = GetModlogResponse;
@ -74,36 +91,36 @@ impl Perform for GetModlog {
let data: &GetModlog = &self; let data: &GetModlog = &self;
let community_id = data.community_id; let community_id = data.community_id;
let mod_person_id = data.mod_person_id; let mod_user_id = data.mod_user_id;
let page = data.page; let page = data.page;
let limit = data.limit; let limit = data.limit;
let removed_posts = blocking(context.pool(), move |conn| { let removed_posts = blocking(context.pool(), move |conn| {
ModRemovePostView::list(conn, community_id, mod_person_id, page, limit) ModRemovePostView::list(conn, community_id, mod_user_id, page, limit)
}) })
.await??; .await??;
let locked_posts = blocking(context.pool(), move |conn| { let locked_posts = blocking(context.pool(), move |conn| {
ModLockPostView::list(conn, community_id, mod_person_id, page, limit) ModLockPostView::list(conn, community_id, mod_user_id, page, limit)
}) })
.await??; .await??;
let stickied_posts = blocking(context.pool(), move |conn| { let stickied_posts = blocking(context.pool(), move |conn| {
ModStickyPostView::list(conn, community_id, mod_person_id, page, limit) ModStickyPostView::list(conn, community_id, mod_user_id, page, limit)
}) })
.await??; .await??;
let removed_comments = blocking(context.pool(), move |conn| { let removed_comments = blocking(context.pool(), move |conn| {
ModRemoveCommentView::list(conn, community_id, mod_person_id, page, limit) ModRemoveCommentView::list(conn, community_id, mod_user_id, page, limit)
}) })
.await??; .await??;
let banned_from_community = blocking(context.pool(), move |conn| { let banned_from_community = blocking(context.pool(), move |conn| {
ModBanFromCommunityView::list(conn, community_id, mod_person_id, page, limit) ModBanFromCommunityView::list(conn, community_id, mod_user_id, page, limit)
}) })
.await??; .await??;
let added_to_community = blocking(context.pool(), move |conn| { let added_to_community = blocking(context.pool(), move |conn| {
ModAddCommunityView::list(conn, community_id, mod_person_id, page, limit) ModAddCommunityView::list(conn, community_id, mod_user_id, page, limit)
}) })
.await??; .await??;
@ -111,9 +128,9 @@ impl Perform for GetModlog {
let (removed_communities, banned, added) = if data.community_id.is_none() { let (removed_communities, banned, added) = if data.community_id.is_none() {
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
Ok(( Ok((
ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?, ModRemoveCommunityView::list(conn, mod_user_id, page, limit)?,
ModBanView::list(conn, mod_person_id, page, limit)?, ModBanView::list(conn, mod_user_id, page, limit)?,
ModAddView::list(conn, mod_person_id, page, limit)?, ModAddView::list(conn, mod_user_id, page, limit)?,
)) as Result<_, LemmyError> )) as Result<_, LemmyError>
}) })
.await?? .await??
@ -149,23 +166,23 @@ impl Perform for CreateSite {
let read_site = move |conn: &'_ _| Site::read_simple(conn); let read_site = move |conn: &'_ _| Site::read_simple(conn);
if blocking(context.pool(), read_site).await?.is_ok() { if blocking(context.pool(), read_site).await?.is_ok() {
return Err(ApiError::err("site_already_exists").into()); return Err(APIError::err("site_already_exists").into());
}; };
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
check_slurs(&data.name)?; check_slurs(&data.name)?;
check_slurs_opt(&data.description)?; check_slurs_opt(&data.description)?;
// Make sure user is an admin // Make sure user is an admin
is_admin(&local_user_view)?; is_admin(context.pool(), user.id).await?;
let site_form = SiteForm { let site_form = SiteForm {
name: data.name.to_owned(), name: data.name.to_owned(),
description: data.description.to_owned(), description: data.description.to_owned(),
icon: Some(data.icon.to_owned().map(|url| url.into())), icon: Some(data.icon.to_owned()),
banner: Some(data.banner.to_owned().map(|url| url.into())), banner: Some(data.banner.to_owned()),
creator_id: local_user_view.person.id, creator_id: user.id,
enable_downvotes: data.enable_downvotes, enable_downvotes: data.enable_downvotes,
open_registration: data.open_registration, open_registration: data.open_registration,
enable_nsfw: data.enable_nsfw, enable_nsfw: data.enable_nsfw,
@ -174,7 +191,7 @@ impl Perform for CreateSite {
let create_site = move |conn: &'_ _| Site::create(conn, &site_form); let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
if blocking(context.pool(), create_site).await?.is_err() { if blocking(context.pool(), create_site).await?.is_err() {
return Err(ApiError::err("site_already_exists").into()); return Err(APIError::err("site_already_exists").into());
} }
let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??; let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
@ -192,18 +209,18 @@ impl Perform for EditSite {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<SiteResponse, LemmyError> { ) -> Result<SiteResponse, LemmyError> {
let data: &EditSite = &self; let data: &EditSite = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
check_slurs(&data.name)?; check_slurs(&data.name)?;
check_slurs_opt(&data.description)?; check_slurs_opt(&data.description)?;
// Make sure user is an admin // Make sure user is an admin
is_admin(&local_user_view)?; is_admin(context.pool(), user.id).await?;
let found_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??; let found_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
let icon = diesel_option_overwrite_to_url(&data.icon)?; let icon = diesel_option_overwrite(&data.icon);
let banner = diesel_option_overwrite_to_url(&data.banner)?; let banner = diesel_option_overwrite(&data.banner);
let site_form = SiteForm { let site_form = SiteForm {
name: data.name.to_owned(), name: data.name.to_owned(),
@ -219,7 +236,7 @@ impl Perform for EditSite {
let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form); let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
if blocking(context.pool(), update_site).await?.is_err() { if blocking(context.pool(), update_site).await?.is_err() {
return Err(ApiError::err("couldnt_update_site").into()); return Err(APIError::err("couldnt_update_site").into());
} }
let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??; let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
@ -251,12 +268,13 @@ impl Perform for GetSite {
Ok(site_view) => Some(site_view), Ok(site_view) => Some(site_view),
// If the site isn't created yet, check the setup // If the site isn't created yet, check the setup
Err(_) => { Err(_) => {
if let Some(setup) = Settings::get().setup().as_ref() { if let Some(setup) = Settings::get().setup.as_ref() {
let register = Register { let register = Register {
username: setup.admin_username.to_owned(), username: setup.admin_username.to_owned(),
email: setup.admin_email.to_owned(), email: setup.admin_email.to_owned(),
password: setup.admin_password.to_owned(), password: setup.admin_password.to_owned(),
password_verify: setup.admin_password.to_owned(), password_verify: setup.admin_password.to_owned(),
admin: true,
show_nsfw: true, show_nsfw: true,
captcha_uuid: None, captcha_uuid: None,
captcha_answer: None, captcha_answer: None,
@ -283,20 +301,20 @@ impl Perform for GetSite {
} }
}; };
let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??; let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
// Make sure the site creator is the top admin // Make sure the site creator is the top admin
if let Some(site_view) = site_view.to_owned() { if let Some(site_view) = site_view.to_owned() {
let site_creator_id = site_view.creator.id; let site_creator_id = site_view.creator.id;
// TODO investigate why this is sometimes coming back null // TODO investigate why this is sometimes coming back null
// Maybe user_.admin isn't being set to true? // Maybe user_.admin isn't being set to true?
if let Some(creator_index) = admins.iter().position(|r| r.person.id == site_creator_id) { if let Some(creator_index) = admins.iter().position(|r| r.user.id == site_creator_id) {
let creator_person = admins.remove(creator_index); let creator_user = admins.remove(creator_index);
admins.insert(0, creator_person); admins.insert(0, creator_user);
} }
} }
let banned = blocking(context.pool(), move |conn| PersonViewSafe::banned(conn)).await??; let banned = blocking(context.pool(), move |conn| UserViewSafe::banned(conn)).await??;
let online = context let online = context
.chat_server() .chat_server()
@ -304,8 +322,14 @@ impl Perform for GetSite {
.await .await
.unwrap_or(1); .unwrap_or(1);
let my_user = get_local_user_settings_view_from_jwt_opt(&data.auth, context.pool()).await?; let my_user = get_user_from_jwt_opt(&data.auth, context.pool())
let federated_instances = build_federated_instances(context.pool()).await?; .await?
.map(|mut u| {
u.password_encrypted = "".to_string();
u.private_key = None;
u.public_key = None;
u
});
Ok(GetSiteResponse { Ok(GetSiteResponse {
site_view, site_view,
@ -314,7 +338,7 @@ impl Perform for GetSite {
online, online,
version: version::VERSION.to_string(), version: version::VERSION.to_string(),
my_user, my_user,
federated_instances, federated_instances: linked_instances(context.pool()).await?,
}) })
} }
} }
@ -335,8 +359,8 @@ impl Perform for Search {
Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e), Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
} }
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
let person_id = local_user_view.map(|u| u.person.id); let user_id = user.map(|u| u.id);
let type_ = SearchType::from_str(&data.type_)?; let type_ = SearchType::from_str(&data.type_)?;
@ -361,7 +385,7 @@ impl Perform for Search {
.show_nsfw(true) .show_nsfw(true)
.community_id(community_id) .community_id(community_id)
.community_name(community_name) .community_name(community_name)
.my_person_id(person_id) .my_user_id(user_id)
.search_term(q) .search_term(q)
.page(page) .page(page)
.limit(limit) .limit(limit)
@ -374,7 +398,7 @@ impl Perform for Search {
CommentQueryBuilder::create(&conn) CommentQueryBuilder::create(&conn)
.sort(&sort) .sort(&sort)
.search_term(q) .search_term(q)
.my_person_id(person_id) .my_user_id(user_id)
.page(page) .page(page)
.limit(limit) .limit(limit)
.list() .list()
@ -386,7 +410,6 @@ impl Perform for Search {
CommunityQueryBuilder::create(conn) CommunityQueryBuilder::create(conn)
.sort(&sort) .sort(&sort)
.search_term(q) .search_term(q)
.my_person_id(person_id)
.page(page) .page(page)
.limit(limit) .limit(limit)
.list() .list()
@ -395,7 +418,7 @@ impl Perform for Search {
} }
SearchType::Users => { SearchType::Users => {
users = blocking(context.pool(), move |conn| { users = blocking(context.pool(), move |conn| {
PersonQueryBuilder::create(conn) UserQueryBuilder::create(conn)
.sort(&sort) .sort(&sort)
.search_term(q) .search_term(q)
.page(page) .page(page)
@ -411,7 +434,7 @@ impl Perform for Search {
.show_nsfw(true) .show_nsfw(true)
.community_id(community_id) .community_id(community_id)
.community_name(community_name) .community_name(community_name)
.my_person_id(person_id) .my_user_id(user_id)
.search_term(q) .search_term(q)
.page(page) .page(page)
.limit(limit) .limit(limit)
@ -426,7 +449,7 @@ impl Perform for Search {
CommentQueryBuilder::create(conn) CommentQueryBuilder::create(conn)
.sort(&sort) .sort(&sort)
.search_term(q) .search_term(q)
.my_person_id(person_id) .my_user_id(user_id)
.page(page) .page(page)
.limit(limit) .limit(limit)
.list() .list()
@ -440,7 +463,6 @@ impl Perform for Search {
CommunityQueryBuilder::create(conn) CommunityQueryBuilder::create(conn)
.sort(&sort) .sort(&sort)
.search_term(q) .search_term(q)
.my_person_id(person_id)
.page(page) .page(page)
.limit(limit) .limit(limit)
.list() .list()
@ -451,7 +473,7 @@ impl Perform for Search {
let sort = SortType::from_str(&data.sort)?; let sort = SortType::from_str(&data.sort)?;
users = blocking(context.pool(), move |conn| { users = blocking(context.pool(), move |conn| {
PersonQueryBuilder::create(conn) UserQueryBuilder::create(conn)
.sort(&sort) .sort(&sort)
.search_term(q) .search_term(q)
.page(page) .page(page)
@ -465,7 +487,6 @@ impl Perform for Search {
PostQueryBuilder::create(conn) PostQueryBuilder::create(conn)
.sort(&sort) .sort(&sort)
.show_nsfw(true) .show_nsfw(true)
.my_person_id(person_id)
.community_id(community_id) .community_id(community_id)
.community_name(community_name) .community_name(community_name)
.url_search(q) .url_search(q)
@ -498,27 +519,32 @@ impl Perform for TransferSite {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetSiteResponse, LemmyError> { ) -> Result<GetSiteResponse, LemmyError> {
let data: &TransferSite = &self; let data: &TransferSite = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let mut user = get_user_from_jwt(&data.auth, context.pool()).await?;
is_admin(&local_user_view)?; is_admin(context.pool(), user.id).await?;
// TODO add a User_::read_safe() for this.
user.password_encrypted = "".to_string();
user.private_key = None;
user.public_key = None;
let read_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??; let read_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
// Make sure user is the creator // Make sure user is the creator
if read_site.creator_id != local_user_view.person.id { if read_site.creator_id != user.id {
return Err(ApiError::err("not_an_admin").into()); return Err(APIError::err("not_an_admin").into());
} }
let new_creator_id = data.person_id; let new_creator_id = data.user_id;
let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id); let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
if blocking(context.pool(), transfer_site).await?.is_err() { if blocking(context.pool(), transfer_site).await?.is_err() {
return Err(ApiError::err("couldnt_update_site").into()); return Err(APIError::err("couldnt_update_site").into());
}; };
// Mod tables // Mod tables
let form = ModAddForm { let form = ModAddForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
other_person_id: data.person_id, other_user_id: data.user_id,
removed: Some(false), removed: Some(false),
}; };
@ -526,18 +552,15 @@ impl Perform for TransferSite {
let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??; let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??; let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
let creator_index = admins let creator_index = admins
.iter() .iter()
.position(|r| r.person.id == site_view.creator.id) .position(|r| r.user.id == site_view.creator.id)
.context(location_info!())?; .context(location_info!())?;
let creator_person = admins.remove(creator_index); let creator_user = admins.remove(creator_index);
admins.insert(0, creator_person); admins.insert(0, creator_user);
let banned = blocking(context.pool(), move |conn| PersonViewSafe::banned(conn)).await??; let banned = blocking(context.pool(), move |conn| UserViewSafe::banned(conn)).await??;
let federated_instances = build_federated_instances(context.pool()).await?;
let my_user = Some(get_local_user_settings_view_from_jwt(&data.auth, context.pool()).await?);
Ok(GetSiteResponse { Ok(GetSiteResponse {
site_view: Some(site_view), site_view: Some(site_view),
@ -545,8 +568,8 @@ impl Perform for TransferSite {
banned, banned,
online: 0, online: 0,
version: version::VERSION.to_string(), version: version::VERSION.to_string(),
my_user, my_user: Some(user),
federated_instances, federated_instances: linked_instances(context.pool()).await?,
}) })
} }
} }
@ -561,10 +584,10 @@ impl Perform for GetSiteConfig {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetSiteConfigResponse, LemmyError> { ) -> Result<GetSiteConfigResponse, LemmyError> {
let data: &GetSiteConfig = &self; let data: &GetSiteConfig = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Only let admins read this // Only let admins read this
is_admin(&local_user_view)?; is_admin(context.pool(), user.id).await?;
let config_hjson = Settings::read_config_file()?; let config_hjson = Settings::read_config_file()?;
@ -582,15 +605,16 @@ impl Perform for SaveSiteConfig {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetSiteConfigResponse, LemmyError> { ) -> Result<GetSiteConfigResponse, LemmyError> {
let data: &SaveSiteConfig = &self; let data: &SaveSiteConfig = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Only let admins read this // Only let admins read this
is_admin(&local_user_view)?; let user_id = user.id;
is_admin(context.pool(), user_id).await?;
// Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
let config_hjson = match Settings::save_config_file(&data.config_hjson) { let config_hjson = match Settings::save_config_file(&data.config_hjson) {
Ok(config_hjson) => config_hjson, Ok(config_hjson) => config_hjson,
Err(_e) => return Err(ApiError::err("couldnt_update_site").into()), Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
}; };
Ok(GetSiteConfigResponse { config_hjson }) Ok(GetSiteConfigResponse { config_hjson })

File diff suppressed because it is too large Load diff

1
lemmy_api/src/version.rs Normal file
View file

@ -0,0 +1 @@
pub const VERSION: &str = "0.9.0-rc.4";

View file

@ -1,52 +1,52 @@
[package] [package]
name = "lemmy_apub" name = "lemmy_apub"
version = "0.1.0" version = "0.1.0"
authors = ["Felix Ableitner <me@nutomic.com>"]
edition = "2018" edition = "2018"
[lib] [lib]
name = "lemmy_apub" name = "lemmy_apub"
path = "src/lib.rs" path = "src/lib.rs"
doctest = false
[dependencies] [dependencies]
lemmy_utils = { path = "../utils" } lemmy_utils = { path = "../lemmy_utils" }
lemmy_db_queries = { path = "../db_queries" } lemmy_db_queries = { path = "../lemmy_db_queries" }
lemmy_db_schema = { path = "../db_schema" } lemmy_db_schema = { path = "../lemmy_db_schema" }
lemmy_db_views = { path = "../db_views" } lemmy_db_views = { path = "../lemmy_db_views" }
lemmy_db_views_actor = { path = "../db_views_actor" } lemmy_db_views_actor = { path = "../lemmy_db_views_actor" }
lemmy_api_structs = { path = "../api_structs" } lemmy_structs = { path = "../lemmy_structs" }
lemmy_websocket = { path = "../websocket" } lemmy_websocket = { path = "../lemmy_websocket" }
diesel = "1.4.5" diesel = "1.4.5"
activitystreams = "0.7.0-alpha.10" activitystreams = "0.7.0-alpha.8"
activitystreams-ext = "0.1.0-alpha.2" activitystreams-ext = "0.1.0-alpha.2"
bcrypt = "0.9.0" bcrypt = "0.9.0"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
serde_json = { version = "1.0.61", features = ["preserve_order"] } serde_json = { version = "1.0.60", features = ["preserve_order"] }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.118", features = ["derive"] }
actix = "0.10.0" actix = "0.10.0"
actix-web = { version = "3.3.2", default-features = false } actix-web = { version = "3.3.2", default-features = false }
actix-rt = { version = "1.1.1", default-features = false } actix-rt = { version = "1.1.1", default-features = false }
awc = { version = "2.0.3", default-features = false } awc = { version = "2.0.3", default-features = false }
log = "0.4.14" log = "0.4.11"
rand = "0.8.3" rand = "0.8.0"
strum = "0.20.0" strum = "0.20.0"
strum_macros = "0.20.1" strum_macros = "0.20.1"
lazy_static = "1.4.0" lazy_static = "1.4.0"
url = { version = "2.2.1", features = ["serde"] } url = { version = "2.2.0", features = ["serde"] }
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
openssl = "0.10.32" openssl = "0.10.31"
http = "0.2.3" http = "0.2.2"
http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] } http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
http-signature-normalization-reqwest = { version = "0.1.3", default-features = false, features = ["sha-2"] } http-signature-normalization-reqwest = { version = "0.1.3", default-features = false, features = ["sha-2"] }
base64 = "0.13.0" base64 = "0.13.0"
tokio = "0.3.6" tokio = "0.3.6"
futures = "0.3.12" futures = "0.3.8"
itertools = "0.10.0" itertools = "0.9.0"
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "0.8.1", features = ["serde", "v4"] }
sha2 = "0.9.3" sha2 = "0.9.2"
async-trait = "0.1.42" async-trait = "0.1.42"
anyhow = "1.0.38" anyhow = "1.0.36"
thiserror = "1.0.23" thiserror = "1.0.22"
background-jobs = "0.8.0" background-jobs = "0.8.0"
reqwest = { version = "0.10.10", features = ["json"] } reqwest = { version = "0.10.10", features = ["json"] }
backtrace = "0.3.56" backtrace = "0.3.55"

View file

@ -1,16 +1,16 @@
use crate::{activities::receive::get_actor_as_person, objects::FromApub, ActorType, NoteExt}; use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, NoteExt};
use activitystreams::{ use activitystreams::{
activity::{ActorAndObjectRefExt, Create, Dislike, Like, Remove, Update}, activity::{ActorAndObjectRefExt, Create, Dislike, Like, Remove, Update},
base::ExtendsExt, base::ExtendsExt,
}; };
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::{blocking, comment::CommentResponse, send_local_notifs};
use lemmy_db_queries::{source::comment::Comment_, Crud, Likeable}; use lemmy_db_queries::{source::comment::Comment_, Crud, Likeable};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
comment::{Comment, CommentLike, CommentLikeForm}, comment::{Comment, CommentLike, CommentLikeForm},
post::Post, post::Post,
}; };
use lemmy_db_views::comment_view::CommentView; use lemmy_db_views::comment_view::CommentView;
use lemmy_structs::{blocking, comment::CommentResponse, send_local_notifs};
use lemmy_utils::{location_info, utils::scrape_text_for_mentions, LemmyError}; use lemmy_utils::{location_info, utils::scrape_text_for_mentions, LemmyError};
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation}; use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
@ -19,11 +19,11 @@ pub(crate) async fn receive_create_comment(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let person = get_actor_as_person(&create, context, request_counter).await?; let user = get_actor_as_user(&create, context, request_counter).await?;
let note = NoteExt::from_any_base(create.object().to_owned().one().context(location_info!())?)? let note = NoteExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?; .context(location_info!())?;
let comment = Comment::from_apub(&note, context, person.actor_id(), request_counter).await?; let comment = Comment::from_apub(&note, context, user.actor_id()?, request_counter).await?;
let post_id = comment.post_id; let post_id = comment.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -33,15 +33,8 @@ pub(crate) async fn receive_create_comment(
// Its much easier to scrape them from the comment body, since the API has to do that // Its much easier to scrape them from the comment body, since the API has to do that
// anyway. // anyway.
let mentions = scrape_text_for_mentions(&comment.content); let mentions = scrape_text_for_mentions(&comment.content);
let recipient_ids = send_local_notifs( let recipient_ids =
mentions, send_local_notifs(mentions, comment.clone(), &user, post, context.pool(), true).await?;
comment.clone(),
person,
post,
context.pool(),
true,
)
.await?;
// Refetch the view // Refetch the view
let comment_view = blocking(context.pool(), move |conn| { let comment_view = blocking(context.pool(), move |conn| {
@ -71,9 +64,9 @@ pub(crate) async fn receive_update_comment(
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let note = NoteExt::from_any_base(update.object().to_owned().one().context(location_info!())?)? let note = NoteExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?; .context(location_info!())?;
let person = get_actor_as_person(&update, context, request_counter).await?; let user = get_actor_as_user(&update, context, request_counter).await?;
let comment = Comment::from_apub(&note, context, person.actor_id(), request_counter).await?; let comment = Comment::from_apub(&note, context, user.actor_id()?, request_counter).await?;
let comment_id = comment.id; let comment_id = comment.id;
let post_id = comment.post_id; let post_id = comment.post_id;
@ -81,7 +74,7 @@ pub(crate) async fn receive_update_comment(
let mentions = scrape_text_for_mentions(&comment.content); let mentions = scrape_text_for_mentions(&comment.content);
let recipient_ids = let recipient_ids =
send_local_notifs(mentions, comment, person, post, context.pool(), false).await?; send_local_notifs(mentions, comment, &user, post, context.pool(), false).await?;
// Refetch the view // Refetch the view
let comment_view = blocking(context.pool(), move |conn| { let comment_view = blocking(context.pool(), move |conn| {
@ -110,18 +103,18 @@ pub(crate) async fn receive_like_comment(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let person = get_actor_as_person(&like, context, request_counter).await?; let user = get_actor_as_user(&like, context, request_counter).await?;
let comment_id = comment.id; let comment_id = comment.id;
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
comment_id, comment_id,
post_id: comment.post_id, post_id: comment.post_id,
person_id: person.id, user_id: user.id,
score: 1, score: 1,
}; };
let person_id = person.id; let user_id = user.id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
CommentLike::remove(conn, person_id, comment_id)?; CommentLike::remove(conn, user_id, comment_id)?;
CommentLike::like(conn, &like_form) CommentLike::like(conn, &like_form)
}) })
.await??; .await??;
@ -155,18 +148,18 @@ pub(crate) async fn receive_dislike_comment(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let person = get_actor_as_person(&dislike, context, request_counter).await?; let user = get_actor_as_user(&dislike, context, request_counter).await?;
let comment_id = comment.id; let comment_id = comment.id;
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
comment_id, comment_id,
post_id: comment.post_id, post_id: comment.post_id,
person_id: person.id, user_id: user.id,
score: -1, score: -1,
}; };
let person_id = person.id; let user_id = user.id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
CommentLike::remove(conn, person_id, comment_id)?; CommentLike::remove(conn, user_id, comment_id)?;
CommentLike::like(conn, &like_form) CommentLike::like(conn, &like_form)
}) })
.await??; .await??;

View file

@ -1,9 +1,9 @@
use crate::activities::receive::get_actor_as_person; use crate::activities::receive::get_actor_as_user;
use activitystreams::activity::{Dislike, Like}; use activitystreams::activity::{Dislike, Like};
use lemmy_api_structs::{blocking, comment::CommentResponse};
use lemmy_db_queries::{source::comment::Comment_, Likeable}; use lemmy_db_queries::{source::comment::Comment_, Likeable};
use lemmy_db_schema::source::comment::{Comment, CommentLike}; use lemmy_db_schema::source::comment::{Comment, CommentLike};
use lemmy_db_views::comment_view::CommentView; use lemmy_db_views::comment_view::CommentView;
use lemmy_structs::{blocking, comment::CommentResponse};
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation}; use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
@ -13,12 +13,12 @@ pub(crate) async fn receive_undo_like_comment(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let person = get_actor_as_person(like, context, request_counter).await?; let user = get_actor_as_user(like, context, request_counter).await?;
let comment_id = comment.id; let comment_id = comment.id;
let person_id = person.id; let user_id = user.id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
CommentLike::remove(conn, person_id, comment_id) CommentLike::remove(conn, user_id, comment_id)
}) })
.await??; .await??;
@ -51,12 +51,12 @@ pub(crate) async fn receive_undo_dislike_comment(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let person = get_actor_as_person(dislike, context, request_counter).await?; let user = get_actor_as_user(dislike, context, request_counter).await?;
let comment_id = comment.id; let comment_id = comment.id;
let person_id = person.id; let user_id = user.id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
CommentLike::remove(conn, person_id, comment_id) CommentLike::remove(conn, user_id, comment_id)
}) })
.await??; .await??;

View file

@ -4,10 +4,10 @@ use activitystreams::{
base::{AnyBase, ExtendsExt}, base::{AnyBase, ExtendsExt},
}; };
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::{blocking, community::CommunityResponse};
use lemmy_db_queries::{source::community::Community_, ApubObject}; use lemmy_db_queries::{source::community::Community_, ApubObject};
use lemmy_db_schema::source::community::Community; use lemmy_db_schema::source::community::Community;
use lemmy_db_views_actor::community_view::CommunityView; use lemmy_db_views_actor::community_view::CommunityView;
use lemmy_structs::{blocking, community::CommunityResponse};
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation}; use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
use url::Url; use url::Url;
@ -55,7 +55,7 @@ pub(crate) async fn receive_remove_community(
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &community_uri.into()) Community::read_from_apub_id(conn, community_uri.as_str())
}) })
.await??; .await??;
@ -137,7 +137,7 @@ pub(crate) async fn receive_undo_remove_community(
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &community_uri.into()) Community::read_from_apub_id(conn, community_uri.as_str())
}) })
.await??; .await??;

View file

@ -1,11 +1,11 @@
use crate::fetcher::person::get_or_fetch_and_upsert_person; use crate::fetcher::user::get_or_fetch_and_upsert_user;
use activitystreams::{ use activitystreams::{
activity::{ActorAndObjectRef, ActorAndObjectRefExt}, activity::{ActorAndObjectRef, ActorAndObjectRefExt},
base::{AsBase, BaseExt}, base::{AsBase, BaseExt},
error::DomainError, error::DomainError,
}; };
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use lemmy_db_schema::source::person::Person; use lemmy_db_schema::source::user::User_;
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use log::debug; use log::debug;
@ -28,18 +28,18 @@ where
Err(anyhow!("Activity not supported").into()) Err(anyhow!("Activity not supported").into())
} }
/// Reads the actor field of an activity and returns the corresponding `Person`. /// Reads the actor field of an activity and returns the corresponding `User_`.
pub(crate) async fn get_actor_as_person<T, A>( pub(crate) async fn get_actor_as_user<T, A>(
activity: &T, activity: &T,
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<Person, LemmyError> ) -> Result<User_, LemmyError>
where where
T: AsBase<A> + ActorAndObjectRef, T: AsBase<A> + ActorAndObjectRef,
{ {
let actor = activity.actor()?; let actor = activity.actor()?;
let person_uri = actor.as_single_xsd_any_uri().context(location_info!())?; let user_uri = actor.as_single_xsd_any_uri().context(location_info!())?;
get_or_fetch_and_upsert_person(&person_uri, context, request_counter).await get_or_fetch_and_upsert_user(&user_uri, context, request_counter).await
} }
/// Ensure that the ID of an incoming activity comes from the same domain as the actor. Optionally /// Ensure that the ID of an incoming activity comes from the same domain as the actor. Optionally

View file

@ -1,13 +1,13 @@
use crate::{activities::receive::get_actor_as_person, objects::FromApub, ActorType, PageExt}; use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, PageExt};
use activitystreams::{ use activitystreams::{
activity::{Create, Dislike, Like, Remove, Update}, activity::{Create, Dislike, Like, Remove, Update},
prelude::*, prelude::*,
}; };
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::{blocking, post::PostResponse};
use lemmy_db_queries::{source::post::Post_, Likeable}; use lemmy_db_queries::{source::post::Post_, Likeable};
use lemmy_db_schema::source::post::{Post, PostLike, PostLikeForm}; use lemmy_db_schema::source::post::{Post, PostLike, PostLikeForm};
use lemmy_db_views::post_view::PostView; use lemmy_db_views::post_view::PostView;
use lemmy_structs::{blocking, post::PostResponse};
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation}; use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
@ -16,11 +16,11 @@ pub(crate) async fn receive_create_post(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let person = get_actor_as_person(&create, context, request_counter).await?; let user = get_actor_as_user(&create, context, request_counter).await?;
let page = PageExt::from_any_base(create.object().to_owned().one().context(location_info!())?)? let page = PageExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?; .context(location_info!())?;
let post = Post::from_apub(&page, context, person.actor_id(), request_counter).await?; let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?;
// Refetch the view // Refetch the view
let post_id = post.id; let post_id = post.id;
@ -45,11 +45,11 @@ pub(crate) async fn receive_update_post(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let person = get_actor_as_person(&update, context, request_counter).await?; let user = get_actor_as_user(&update, context, request_counter).await?;
let page = PageExt::from_any_base(update.object().to_owned().one().context(location_info!())?)? let page = PageExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?; .context(location_info!())?;
let post = Post::from_apub(&page, context, person.actor_id(), request_counter).await?; let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?;
let post_id = post.id; let post_id = post.id;
// Refetch the view // Refetch the view
@ -75,17 +75,17 @@ pub(crate) async fn receive_like_post(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let person = get_actor_as_person(&like, context, request_counter).await?; let user = get_actor_as_user(&like, context, request_counter).await?;
let post_id = post.id; let post_id = post.id;
let like_form = PostLikeForm { let like_form = PostLikeForm {
post_id, post_id,
person_id: person.id, user_id: user.id,
score: 1, score: 1,
}; };
let person_id = person.id; let user_id = user.id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
PostLike::remove(conn, person_id, post_id)?; PostLike::remove(conn, user_id, post_id)?;
PostLike::like(conn, &like_form) PostLike::like(conn, &like_form)
}) })
.await??; .await??;
@ -113,17 +113,17 @@ pub(crate) async fn receive_dislike_post(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let person = get_actor_as_person(&dislike, context, request_counter).await?; let user = get_actor_as_user(&dislike, context, request_counter).await?;
let post_id = post.id; let post_id = post.id;
let like_form = PostLikeForm { let like_form = PostLikeForm {
post_id, post_id,
person_id: person.id, user_id: user.id,
score: -1, score: -1,
}; };
let person_id = person.id; let user_id = user.id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
PostLike::remove(conn, person_id, post_id)?; PostLike::remove(conn, user_id, post_id)?;
PostLike::like(conn, &like_form) PostLike::like(conn, &like_form)
}) })
.await??; .await??;

View file

@ -1,9 +1,9 @@
use crate::activities::receive::get_actor_as_person; use crate::activities::receive::get_actor_as_user;
use activitystreams::activity::{Dislike, Like}; use activitystreams::activity::{Dislike, Like};
use lemmy_api_structs::{blocking, post::PostResponse};
use lemmy_db_queries::{source::post::Post_, Likeable}; use lemmy_db_queries::{source::post::Post_, Likeable};
use lemmy_db_schema::source::post::{Post, PostLike}; use lemmy_db_schema::source::post::{Post, PostLike};
use lemmy_db_views::post_view::PostView; use lemmy_db_views::post_view::PostView;
use lemmy_structs::{blocking, post::PostResponse};
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation}; use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
@ -13,12 +13,12 @@ pub(crate) async fn receive_undo_like_post(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let person = get_actor_as_person(like, context, request_counter).await?; let user = get_actor_as_user(like, context, request_counter).await?;
let post_id = post.id; let post_id = post.id;
let person_id = person.id; let user_id = user.id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
PostLike::remove(conn, person_id, post_id) PostLike::remove(conn, user_id, post_id)
}) })
.await??; .await??;
@ -45,12 +45,12 @@ pub(crate) async fn receive_undo_dislike_post(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let person = get_actor_as_person(dislike, context, request_counter).await?; let user = get_actor_as_user(dislike, context, request_counter).await?;
let post_id = post.id; let post_id = post.id;
let person_id = person.id; let user_id = user.id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
PostLike::remove(conn, person_id, post_id) PostLike::remove(conn, user_id, post_id)
}) })
.await??; .await??;

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
activities::receive::verify_activity_domains_valid, activities::receive::verify_activity_domains_valid,
check_is_apub_id_valid, check_is_apub_id_valid,
fetcher::person::get_or_fetch_and_upsert_person, fetcher::user::get_or_fetch_and_upsert_user,
inbox::get_activity_to_and_cc, inbox::get_activity_to_and_cc,
objects::FromApub, objects::FromApub,
NoteExt, NoteExt,
@ -13,10 +13,10 @@ use activitystreams::{
public, public,
}; };
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use lemmy_api_structs::{blocking, person::PrivateMessageResponse};
use lemmy_db_queries::source::private_message::PrivateMessage_; use lemmy_db_queries::source::private_message::PrivateMessage_;
use lemmy_db_schema::source::private_message::PrivateMessage; use lemmy_db_schema::source::private_message::PrivateMessage;
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView}; use lemmy_db_views::private_message_view::PrivateMessageView;
use lemmy_structs::{blocking, user::PrivateMessageResponse};
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation}; use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
use url::Url; use url::Url;
@ -50,19 +50,12 @@ pub(crate) async fn receive_create_private_message(
private_message_view: message, private_message_view: message,
}; };
// Send notifications to the local recipient, if one exists
let recipient_id = res.private_message_view.recipient.id; let recipient_id = res.private_message_view.recipient.id;
let local_recipient_id = blocking(context.pool(), move |conn| {
LocalUserView::read_person(conn, recipient_id)
})
.await??
.local_user
.id;
context.chat_server().do_send(SendUserRoomMessage { context.chat_server().do_send(SendUserRoomMessage {
op: UserOperation::CreatePrivateMessage, op: UserOperation::CreatePrivateMessage,
response: res, response: res,
local_recipient_id, recipient_id,
websocket_id: None, websocket_id: None,
}); });
@ -98,17 +91,11 @@ pub(crate) async fn receive_update_private_message(
}; };
let recipient_id = res.private_message_view.recipient.id; let recipient_id = res.private_message_view.recipient.id;
let local_recipient_id = blocking(context.pool(), move |conn| {
LocalUserView::read_person(conn, recipient_id)
})
.await??
.local_user
.id;
context.chat_server().do_send(SendUserRoomMessage { context.chat_server().do_send(SendUserRoomMessage {
op: UserOperation::EditPrivateMessage, op: UserOperation::EditPrivateMessage,
response: res, response: res,
local_recipient_id, recipient_id,
websocket_id: None, websocket_id: None,
}); });
@ -136,19 +123,11 @@ pub(crate) async fn receive_delete_private_message(
let res = PrivateMessageResponse { let res = PrivateMessageResponse {
private_message_view: message, private_message_view: message,
}; };
let recipient_id = res.private_message_view.recipient.id; let recipient_id = res.private_message_view.recipient.id;
let local_recipient_id = blocking(context.pool(), move |conn| {
LocalUserView::read_person(conn, recipient_id)
})
.await??
.local_user
.id;
context.chat_server().do_send(SendUserRoomMessage { context.chat_server().do_send(SendUserRoomMessage {
op: UserOperation::EditPrivateMessage, op: UserOperation::EditPrivateMessage,
response: res, response: res,
local_recipient_id, recipient_id,
websocket_id: None, websocket_id: None,
}); });
@ -181,19 +160,11 @@ pub(crate) async fn receive_undo_delete_private_message(
let res = PrivateMessageResponse { let res = PrivateMessageResponse {
private_message_view: message, private_message_view: message,
}; };
let recipient_id = res.private_message_view.recipient.id; let recipient_id = res.private_message_view.recipient.id;
let local_recipient_id = blocking(context.pool(), move |conn| {
LocalUserView::read_person(conn, recipient_id)
})
.await??
.local_user
.id;
context.chat_server().do_send(SendUserRoomMessage { context.chat_server().do_send(SendUserRoomMessage {
op: UserOperation::EditPrivateMessage, op: UserOperation::EditPrivateMessage,
response: res, response: res,
local_recipient_id, recipient_id,
websocket_id: None, websocket_id: None,
}); });
@ -210,19 +181,19 @@ where
{ {
let to_and_cc = get_activity_to_and_cc(activity); let to_and_cc = get_activity_to_and_cc(activity);
if to_and_cc.len() != 1 { if to_and_cc.len() != 1 {
return Err(anyhow!("Private message can only be addressed to one person").into()); return Err(anyhow!("Private message can only be addressed to one user").into());
} }
if to_and_cc.contains(&public()) { if to_and_cc.contains(&public()) {
return Err(anyhow!("Private message cant be public").into()); return Err(anyhow!("Private message cant be public").into());
} }
let person_id = activity let user_id = activity
.actor()? .actor()?
.to_owned() .to_owned()
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
check_is_apub_id_valid(&person_id)?; check_is_apub_id_valid(&user_id)?;
// check that the sender is a person, not a community // check that the sender is a user, not a community
get_or_fetch_and_upsert_person(&person_id, &context, request_counter).await?; get_or_fetch_and_upsert_user(&user_id, &context, request_counter).await?;
Ok(()) Ok(())
} }

View file

@ -2,7 +2,7 @@ use crate::{
activities::send::generate_activity_id, activities::send::generate_activity_id,
activity_queue::{send_comment_mentions, send_to_community}, activity_queue::{send_comment_mentions, send_to_community},
extensions::context::lemmy_context, extensions::context::lemmy_context,
fetcher::person::get_or_fetch_and_upsert_person, fetcher::user::get_or_fetch_and_upsert_user,
objects::ToApub, objects::ToApub,
ActorType, ActorType,
ApubLikeableType, ApubLikeableType,
@ -26,12 +26,12 @@ use activitystreams::{
}; };
use anyhow::anyhow; use anyhow::anyhow;
use itertools::Itertools; use itertools::Itertools;
use lemmy_api_structs::{blocking, WebFingerResponse};
use lemmy_db_queries::{Crud, DbPool}; use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post}; use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post, user::User_};
use lemmy_structs::{blocking, WebFingerResponse};
use lemmy_utils::{ use lemmy_utils::{
request::{retry, RecvError}, request::{retry, RecvError},
settings::structs::Settings, settings::Settings,
utils::{scrape_text_for_mentions, MentionData}, utils::{scrape_text_for_mentions, MentionData},
LemmyError, LemmyError,
}; };
@ -44,8 +44,8 @@ use url::Url;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ApubObjectType for Comment { impl ApubObjectType for Comment {
/// Send out information about a newly created comment, to the followers of the community and /// Send out information about a newly created comment, to the followers of the community and
/// mentioned persons. /// mentioned users.
async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?; let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id; let post_id = self.post_id;
@ -57,17 +57,17 @@ impl ApubObjectType for Comment {
}) })
.await??; .await??;
let maa = collect_non_local_mentions(&self, &community, context).await?; let mut maa = collect_non_local_mentions_and_addresses(&self.content, context).await?;
let mut ccs = vec![community.actor_id()?];
ccs.append(&mut maa.addressed_ccs);
ccs.push(get_comment_parent_creator_id(context.pool(), &self).await?);
let mut create = Create::new( let mut create = Create::new(creator.actor_id.to_owned(), note.into_any_base()?);
creator.actor_id.to_owned().into_inner(),
note.into_any_base()?,
);
create create
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(CreateType::Create)?) .set_id(generate_activity_id(CreateType::Create)?)
.set_to(public()) .set_to(public())
.set_many_ccs(maa.ccs.to_owned()) .set_many_ccs(ccs)
// Set the mention tags // Set the mention tags
.set_many_tags(maa.get_tags()?); .set_many_tags(maa.get_tags()?);
@ -77,8 +77,8 @@ impl ApubObjectType for Comment {
} }
/// Send out information about an edited post, to the followers of the community and mentioned /// Send out information about an edited post, to the followers of the community and mentioned
/// persons. /// users.
async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?; let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id; let post_id = self.post_id;
@ -90,17 +90,17 @@ impl ApubObjectType for Comment {
}) })
.await??; .await??;
let maa = collect_non_local_mentions(&self, &community, context).await?; let mut maa = collect_non_local_mentions_and_addresses(&self.content, context).await?;
let mut ccs = vec![community.actor_id()?];
ccs.append(&mut maa.addressed_ccs);
ccs.push(get_comment_parent_creator_id(context.pool(), &self).await?);
let mut update = Update::new( let mut update = Update::new(creator.actor_id.to_owned(), note.into_any_base()?);
creator.actor_id.to_owned().into_inner(),
note.into_any_base()?,
);
update update
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UpdateType::Update)?) .set_id(generate_activity_id(UpdateType::Update)?)
.set_to(public()) .set_to(public())
.set_many_ccs(maa.ccs.to_owned()) .set_many_ccs(ccs)
// Set the mention tags // Set the mention tags
.set_many_tags(maa.get_tags()?); .set_many_tags(maa.get_tags()?);
@ -109,7 +109,7 @@ impl ApubObjectType for Comment {
Ok(()) Ok(())
} }
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -119,15 +119,12 @@ impl ApubObjectType for Comment {
}) })
.await??; .await??;
let mut delete = Delete::new( let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
delete delete
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?) .set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(delete, &creator, &community, context).await?; send_to_community(delete, &creator, &community, context).await?;
Ok(()) Ok(())
@ -135,7 +132,7 @@ impl ApubObjectType for Comment {
async fn send_undo_delete( async fn send_undo_delete(
&self, &self,
creator: &Person, creator: &User_,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
@ -148,32 +145,26 @@ impl ApubObjectType for Comment {
.await??; .await??;
// Generate a fake delete activity, with the correct object // Generate a fake delete activity, with the correct object
let mut delete = Delete::new( let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
delete delete
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?) .set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
// Undo that fake activity // Undo that fake activity
let mut undo = Undo::new( let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
creator.actor_id.to_owned().into_inner(),
delete.into_any_base()?,
);
undo undo
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?) .set_id(generate_activity_id(UndoType::Undo)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(undo, &creator, &community, context).await?; send_to_community(undo, &creator, &community, context).await?;
Ok(()) Ok(())
} }
async fn send_remove(&self, mod_: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -183,25 +174,18 @@ impl ApubObjectType for Comment {
}) })
.await??; .await??;
let mut remove = Remove::new( let mut remove = Remove::new(mod_.actor_id.to_owned(), Url::parse(&self.ap_id)?);
mod_.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
remove remove
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(RemoveType::Remove)?) .set_id(generate_activity_id(RemoveType::Remove)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(remove, &mod_, &community, context).await?; send_to_community(remove, &mod_, &community, context).await?;
Ok(()) Ok(())
} }
async fn send_undo_remove( async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
&self,
mod_: &Person,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -212,26 +196,20 @@ impl ApubObjectType for Comment {
.await??; .await??;
// Generate a fake delete activity, with the correct object // Generate a fake delete activity, with the correct object
let mut remove = Remove::new( let mut remove = Remove::new(mod_.actor_id.to_owned(), Url::parse(&self.ap_id)?);
mod_.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
remove remove
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(RemoveType::Remove)?) .set_id(generate_activity_id(RemoveType::Remove)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
// Undo that fake activity // Undo that fake activity
let mut undo = Undo::new( let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
mod_.actor_id.to_owned().into_inner(),
remove.into_any_base()?,
);
undo undo
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?) .set_id(generate_activity_id(UndoType::Undo)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(undo, &mod_, &community, context).await?; send_to_community(undo, &mod_, &community, context).await?;
Ok(()) Ok(())
@ -240,7 +218,7 @@ impl ApubObjectType for Comment {
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ApubLikeableType for Comment { impl ApubLikeableType for Comment {
async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -250,21 +228,18 @@ impl ApubLikeableType for Comment {
}) })
.await??; .await??;
let mut like = Like::new( let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
like like
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(LikeType::Like)?) .set_id(generate_activity_id(LikeType::Like)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(like, &creator, &community, context).await?; send_to_community(like, &creator, &community, context).await?;
Ok(()) Ok(())
} }
async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -274,15 +249,12 @@ impl ApubLikeableType for Comment {
}) })
.await??; .await??;
let mut dislike = Dislike::new( let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
dislike dislike
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DislikeType::Dislike)?) .set_id(generate_activity_id(DislikeType::Dislike)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(dislike, &creator, &community, context).await?; send_to_community(dislike, &creator, &community, context).await?;
Ok(()) Ok(())
@ -290,7 +262,7 @@ impl ApubLikeableType for Comment {
async fn send_undo_like( async fn send_undo_like(
&self, &self,
creator: &Person, creator: &User_,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
@ -302,26 +274,20 @@ impl ApubLikeableType for Comment {
}) })
.await??; .await??;
let mut like = Like::new( let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
like like
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DislikeType::Dislike)?) .set_id(generate_activity_id(DislikeType::Dislike)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
// Undo that fake activity // Undo that fake activity
let mut undo = Undo::new( let mut undo = Undo::new(creator.actor_id.to_owned(), like.into_any_base()?);
creator.actor_id.to_owned().into_inner(),
like.into_any_base()?,
);
undo undo
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?) .set_id(generate_activity_id(UndoType::Undo)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(undo, &creator, &community, context).await?; send_to_community(undo, &creator, &community, context).await?;
Ok(()) Ok(())
@ -329,7 +295,7 @@ impl ApubLikeableType for Comment {
} }
struct MentionsAndAddresses { struct MentionsAndAddresses {
ccs: Vec<Url>, addressed_ccs: Vec<Url>,
inboxes: Vec<Url>, inboxes: Vec<Url>,
tags: Vec<Mention>, tags: Vec<Mention>,
} }
@ -346,57 +312,55 @@ impl MentionsAndAddresses {
/// This takes a comment, and builds a list of to_addresses, inboxes, /// This takes a comment, and builds a list of to_addresses, inboxes,
/// and mention tags, so they know where to be sent to. /// and mention tags, so they know where to be sent to.
/// Addresses are the persons / addresses that go in the cc field. /// Addresses are the users / addresses that go in the cc field.
async fn collect_non_local_mentions( async fn collect_non_local_mentions_and_addresses(
comment: &Comment, content: &str,
community: &Community,
context: &LemmyContext, context: &LemmyContext,
) -> Result<MentionsAndAddresses, LemmyError> { ) -> Result<MentionsAndAddresses, LemmyError> {
let parent_creator = get_comment_parent_creator(context.pool(), comment).await?; let mut addressed_ccs = vec![];
let mut addressed_ccs = vec![community.actor_id(), parent_creator.actor_id()];
// Note: dont include community inbox here, as we send to it separately with `send_to_community()`
let mut inboxes = vec![parent_creator.get_shared_inbox_or_inbox_url()];
// Add the mention tag // Add the mention tag
let mut tags = Vec::new(); let mut tags = Vec::new();
// Get the person IDs for any mentions // Get the inboxes for any mentions
let mentions = scrape_text_for_mentions(&comment.content) let mentions = scrape_text_for_mentions(&content)
.into_iter() .into_iter()
// Filter only the non-local ones // Filter only the non-local ones
.filter(|m| !m.is_local()) .filter(|m| !m.is_local())
.collect::<Vec<MentionData>>(); .collect::<Vec<MentionData>>();
let mut mention_inboxes: Vec<Url> = Vec::new();
for mention in &mentions { for mention in &mentions {
// TODO should it be fetching it every time? // TODO should it be fetching it every time?
if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await { if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
debug!("mention actor_id: {}", actor_id); debug!("mention actor_id: {}", actor_id);
addressed_ccs.push(actor_id.to_owned().to_string().parse()?); addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
let mention_person = get_or_fetch_and_upsert_person(&actor_id, context, &mut 0).await?; let mention_user = get_or_fetch_and_upsert_user(&actor_id, context, &mut 0).await?;
inboxes.push(mention_person.get_shared_inbox_or_inbox_url()); let shared_inbox = mention_user.get_shared_inbox_url()?;
mention_inboxes.push(shared_inbox);
let mut mention_tag = Mention::new(); let mut mention_tag = Mention::new();
mention_tag.set_href(actor_id).set_name(mention.full_name()); mention_tag.set_href(actor_id).set_name(mention.full_name());
tags.push(mention_tag); tags.push(mention_tag);
} }
} }
let inboxes = inboxes.into_iter().unique().collect(); let inboxes = mention_inboxes.into_iter().unique().collect();
Ok(MentionsAndAddresses { Ok(MentionsAndAddresses {
ccs: addressed_ccs, addressed_ccs,
inboxes, inboxes,
tags, tags,
}) })
} }
/// Returns the apub ID of the person this comment is responding to. Meaning, in case this is a /// Returns the apub ID of the user this comment is responding to. Meaning, in case this is a
/// top-level comment, the creator of the post, otherwise the creator of the parent comment. /// top-level comment, the creator of the post, otherwise the creator of the parent comment.
async fn get_comment_parent_creator( async fn get_comment_parent_creator_id(
pool: &DbPool, pool: &DbPool,
comment: &Comment, comment: &Comment,
) -> Result<Person, LemmyError> { ) -> Result<Url, LemmyError> {
let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id { let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
let parent_comment = let parent_comment =
blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??; blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
@ -406,10 +370,11 @@ async fn get_comment_parent_creator(
let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??; let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
parent_post.creator_id parent_post.creator_id
}; };
Ok(blocking(pool, move |conn| Person::read(conn, parent_creator_id)).await??) let parent_creator = blocking(pool, move |conn| User_::read(conn, parent_creator_id)).await??;
Ok(parent_creator.actor_id()?)
} }
/// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`, /// Turns a user id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
/// using webfinger. /// using webfinger.
async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> { async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
let fetch_url = format!( let fetch_url = format!(
@ -436,5 +401,7 @@ async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<U
link link
.href .href
.to_owned() .to_owned()
.map(|u| Url::parse(&u))
.transpose()?
.ok_or_else(|| anyhow!("No href found.").into()) .ok_or_else(|| anyhow!("No href found.").into())
} }

View file

@ -3,8 +3,7 @@ use crate::{
activity_queue::{send_activity_single_dest, send_to_community_followers}, activity_queue::{send_activity_single_dest, send_to_community_followers},
check_is_apub_id_valid, check_is_apub_id_valid,
extensions::context::lemmy_context, extensions::context::lemmy_context,
fetcher::person::get_or_fetch_and_upsert_person, fetcher::user::get_or_fetch_and_upsert_user,
insert_activity,
ActorType, ActorType,
}; };
use activitystreams::{ use activitystreams::{
@ -24,22 +23,20 @@ use activitystreams::{
}; };
use anyhow::Context; use anyhow::Context;
use itertools::Itertools; use itertools::Itertools;
use lemmy_api_structs::blocking;
use lemmy_db_queries::DbPool; use lemmy_db_queries::DbPool;
use lemmy_db_schema::source::community::Community; use lemmy_db_schema::source::community::Community;
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView; use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError}; use lemmy_structs::blocking;
use lemmy_utils::{location_info, settings::Settings, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use url::Url; use url::Url;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ActorType for Community { impl ActorType for Community {
fn is_local(&self) -> bool { fn actor_id_str(&self) -> String {
self.local self.actor_id.to_owned()
}
fn actor_id(&self) -> Url {
self.actor_id.to_owned().into_inner()
} }
fn public_key(&self) -> Option<String> { fn public_key(&self) -> Option<String> {
self.public_key.to_owned() self.public_key.to_owned()
} }
@ -47,14 +44,6 @@ impl ActorType for Community {
self.private_key.to_owned() self.private_key.to_owned()
} }
fn get_shared_inbox_or_inbox_url(&self) -> Url {
self
.shared_inbox_url
.clone()
.unwrap_or_else(|| self.inbox_url.to_owned())
.into()
}
async fn send_follow( async fn send_follow(
&self, &self,
_follow_actor_id: &Url, _follow_actor_id: &Url,
@ -71,7 +60,7 @@ impl ActorType for Community {
unimplemented!() unimplemented!()
} }
/// As a local community, accept the follow request from a remote person. /// As a local community, accept the follow request from a remote user.
async fn send_accept_follow( async fn send_accept_follow(
&self, &self,
follow: Follow, follow: Follow,
@ -81,29 +70,26 @@ impl ActorType for Community {
.actor()? .actor()?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let person = get_or_fetch_and_upsert_person(actor_uri, context, &mut 0).await?; let user = get_or_fetch_and_upsert_user(actor_uri, context, &mut 0).await?;
let mut accept = Accept::new( let mut accept = Accept::new(self.actor_id.to_owned(), follow.into_any_base()?);
self.actor_id.to_owned().into_inner(),
follow.into_any_base()?,
);
accept accept
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(AcceptType::Accept)?) .set_id(generate_activity_id(AcceptType::Accept)?)
.set_to(person.actor_id()); .set_to(user.actor_id()?);
send_activity_single_dest(accept, self, person.inbox_url.into(), context).await?; send_activity_single_dest(accept, self, user.get_inbox_url()?, context).await?;
Ok(()) Ok(())
} }
/// If the creator of a community deletes the community, send this to all followers. /// If the creator of a community deletes the community, send this to all followers.
async fn send_delete(&self, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_delete(&self, context: &LemmyContext) -> Result<(), LemmyError> {
let mut delete = Delete::new(self.actor_id(), self.actor_id()); let mut delete = Delete::new(self.actor_id()?, self.actor_id()?);
delete delete
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?) .set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![self.followers_url.clone().into_inner()]); .set_many_ccs(vec![self.get_followers_url()?]);
send_to_community_followers(delete, self, context).await?; send_to_community_followers(delete, self, context).await?;
Ok(()) Ok(())
@ -111,19 +97,19 @@ impl ActorType for Community {
/// If the creator of a community reverts the deletion of a community, send this to all followers. /// If the creator of a community reverts the deletion of a community, send this to all followers.
async fn send_undo_delete(&self, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_undo_delete(&self, context: &LemmyContext) -> Result<(), LemmyError> {
let mut delete = Delete::new(self.actor_id(), self.actor_id()); let mut delete = Delete::new(self.actor_id()?, self.actor_id()?);
delete delete
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?) .set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![self.followers_url.clone().into_inner()]); .set_many_ccs(vec![self.get_followers_url()?]);
let mut undo = Undo::new(self.actor_id(), delete.into_any_base()?); let mut undo = Undo::new(self.actor_id()?, delete.into_any_base()?);
undo undo
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?) .set_id(generate_activity_id(UndoType::Undo)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![self.followers_url.clone().into_inner()]); .set_many_ccs(vec![self.get_followers_url()?]);
send_to_community_followers(undo, self, context).await?; send_to_community_followers(undo, self, context).await?;
Ok(()) Ok(())
@ -131,12 +117,12 @@ impl ActorType for Community {
/// If an admin removes a community, send this to all followers. /// If an admin removes a community, send this to all followers.
async fn send_remove(&self, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_remove(&self, context: &LemmyContext) -> Result<(), LemmyError> {
let mut remove = Remove::new(self.actor_id(), self.actor_id()); let mut remove = Remove::new(self.actor_id()?, self.actor_id()?);
remove remove
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(RemoveType::Remove)?) .set_id(generate_activity_id(RemoveType::Remove)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![self.followers_url.clone().into_inner()]); .set_many_ccs(vec![self.get_followers_url()?]);
send_to_community_followers(remove, self, context).await?; send_to_community_followers(remove, self, context).await?;
Ok(()) Ok(())
@ -144,20 +130,20 @@ impl ActorType for Community {
/// If an admin reverts the removal of a community, send this to all followers. /// If an admin reverts the removal of a community, send this to all followers.
async fn send_undo_remove(&self, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_undo_remove(&self, context: &LemmyContext) -> Result<(), LemmyError> {
let mut remove = Remove::new(self.actor_id(), self.actor_id()); let mut remove = Remove::new(self.actor_id()?, self.actor_id()?);
remove remove
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(RemoveType::Remove)?) .set_id(generate_activity_id(RemoveType::Remove)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![self.followers_url.clone().into_inner()]); .set_many_ccs(vec![self.get_followers_url()?]);
// Undo that fake activity // Undo that fake activity
let mut undo = Undo::new(self.actor_id(), remove.into_any_base()?); let mut undo = Undo::new(self.actor_id()?, remove.into_any_base()?);
undo undo
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(LikeType::Like)?) .set_id(generate_activity_id(LikeType::Like)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![self.followers_url.clone().into_inner()]); .set_many_ccs(vec![self.get_followers_url()?]);
send_to_community_followers(undo, self, context).await?; send_to_community_followers(undo, self, context).await?;
Ok(()) Ok(())
@ -165,26 +151,17 @@ impl ActorType for Community {
/// Wraps an activity sent to the community in an announce, and then sends the announce to all /// Wraps an activity sent to the community in an announce, and then sends the announce to all
/// community followers. /// community followers.
///
/// If we are announcing a local activity, it hasn't been stored in the database yet, and we need
/// to do it here, so that it can be fetched by ID. Remote activities are inserted into DB in the
/// inbox.
async fn send_announce( async fn send_announce(
&self, &self,
activity: AnyBase, activity: AnyBase,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let inner_id = activity.id().context(location_info!())?; let mut announce = Announce::new(self.actor_id.to_owned(), activity);
if inner_id.domain() == Some(&Settings::get().get_hostname_without_port()?) {
insert_activity(inner_id, activity.clone(), true, false, context.pool()).await?;
}
let mut announce = Announce::new(self.actor_id.to_owned().into_inner(), activity);
announce announce
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(AnnounceType::Announce)?) .set_id(generate_activity_id(AnnounceType::Announce)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![self.followers_url.clone().into_inner()]); .set_many_ccs(vec![self.get_followers_url()?]);
send_to_community_followers(announce, self, context).await?; send_to_community_followers(announce, self, context).await?;
@ -192,21 +169,38 @@ impl ActorType for Community {
} }
/// For a given community, returns the inboxes of all followers. /// For a given community, returns the inboxes of all followers.
///
/// TODO: this function is very badly implemented, we should just store shared_inbox_url in
/// CommunityFollowerView
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError> { async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
let id = self.id; let id = self.id;
let follows = blocking(pool, move |conn| { let inboxes = blocking(pool, move |conn| {
CommunityFollowerView::for_community(conn, id) CommunityFollowerView::for_community(conn, id)
}) })
.await??; .await??;
let inboxes = follows let inboxes = inboxes
.into_iter() .into_iter()
.filter(|f| !f.follower.local) .filter(|i| !i.follower.local)
.map(|f| f.follower.shared_inbox_url.unwrap_or(f.follower.inbox_url)) .map(|u| -> Result<Url, LemmyError> {
.map(|i| i.into_inner()) let url = Url::parse(&u.follower.actor_id)?;
.unique() let domain = url.domain().context(location_info!())?;
let port = if let Some(port) = url.port() {
format!(":{}", port)
} else {
"".to_string()
};
Ok(Url::parse(&format!(
"{}://{}{}/inbox",
Settings::get().get_protocol_string(),
domain,
port,
))?)
})
.filter_map(Result::ok)
// Don't send to blocked instances // Don't send to blocked instances
.filter(|inbox| check_is_apub_id_valid(inbox).is_ok()) .filter(|inbox| check_is_apub_id_valid(inbox).is_ok())
.unique()
.collect(); .collect();
Ok(inboxes) Ok(inboxes)

View file

@ -1,12 +1,12 @@
use lemmy_utils::settings::structs::Settings; use lemmy_utils::settings::Settings;
use url::{ParseError, Url}; use url::{ParseError, Url};
use uuid::Uuid; use uuid::Uuid;
pub(crate) mod comment; pub(crate) mod comment;
pub(crate) mod community; pub(crate) mod community;
pub(crate) mod person;
pub(crate) mod post; pub(crate) mod post;
pub(crate) mod private_message; pub(crate) mod private_message;
pub(crate) mod user;
/// Generate a unique ID for an activity, in the format: /// Generate a unique ID for an activity, in the format:
/// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36` /// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36`

View file

@ -21,16 +21,17 @@ use activitystreams::{
prelude::*, prelude::*,
public, public,
}; };
use lemmy_api_structs::blocking;
use lemmy_db_queries::Crud; use lemmy_db_queries::Crud;
use lemmy_db_schema::source::{community::Community, person::Person, post::Post}; use lemmy_db_schema::source::{community::Community, post::Post, user::User_};
use lemmy_structs::blocking;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use url::Url;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ApubObjectType for Post { impl ApubObjectType for Post {
/// Send out information about a newly created post, to the followers of the community. /// Send out information about a newly created post, to the followers of the community.
async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let page = self.to_apub(context.pool()).await?; let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id; let community_id = self.community_id;
@ -39,22 +40,19 @@ impl ApubObjectType for Post {
}) })
.await??; .await??;
let mut create = Create::new( let mut create = Create::new(creator.actor_id.to_owned(), page.into_any_base()?);
creator.actor_id.to_owned().into_inner(),
page.into_any_base()?,
);
create create
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(CreateType::Create)?) .set_id(generate_activity_id(CreateType::Create)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(create, creator, &community, context).await?; send_to_community(create, creator, &community, context).await?;
Ok(()) Ok(())
} }
/// Send out information about an edited post, to the followers of the community. /// Send out information about an edited post, to the followers of the community.
async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let page = self.to_apub(context.pool()).await?; let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id; let community_id = self.community_id;
@ -63,36 +61,30 @@ impl ApubObjectType for Post {
}) })
.await??; .await??;
let mut update = Update::new( let mut update = Update::new(creator.actor_id.to_owned(), page.into_any_base()?);
creator.actor_id.to_owned().into_inner(),
page.into_any_base()?,
);
update update
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UpdateType::Update)?) .set_id(generate_activity_id(UpdateType::Update)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(update, creator, &community, context).await?; send_to_community(update, creator, &community, context).await?;
Ok(()) Ok(())
} }
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id) Community::read(conn, community_id)
}) })
.await??; .await??;
let mut delete = Delete::new( let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
delete delete
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?) .set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(delete, creator, &community, context).await?; send_to_community(delete, creator, &community, context).await?;
Ok(()) Ok(())
@ -100,7 +92,7 @@ impl ApubObjectType for Post {
async fn send_undo_delete( async fn send_undo_delete(
&self, &self,
creator: &Person, creator: &User_,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
@ -109,83 +101,64 @@ impl ApubObjectType for Post {
}) })
.await??; .await??;
let mut delete = Delete::new( let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
delete delete
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?) .set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
// Undo that fake activity // Undo that fake activity
let mut undo = Undo::new( let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
creator.actor_id.to_owned().into_inner(),
delete.into_any_base()?,
);
undo undo
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?) .set_id(generate_activity_id(UndoType::Undo)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(undo, creator, &community, context).await?; send_to_community(undo, creator, &community, context).await?;
Ok(()) Ok(())
} }
async fn send_remove(&self, mod_: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id) Community::read(conn, community_id)
}) })
.await??; .await??;
let mut remove = Remove::new( let mut remove = Remove::new(mod_.actor_id.to_owned(), Url::parse(&self.ap_id)?);
mod_.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
remove remove
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(RemoveType::Remove)?) .set_id(generate_activity_id(RemoveType::Remove)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(remove, mod_, &community, context).await?; send_to_community(remove, mod_, &community, context).await?;
Ok(()) Ok(())
} }
async fn send_undo_remove( async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
&self,
mod_: &Person,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id) Community::read(conn, community_id)
}) })
.await??; .await??;
let mut remove = Remove::new( let mut remove = Remove::new(mod_.actor_id.to_owned(), Url::parse(&self.ap_id)?);
mod_.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
remove remove
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(RemoveType::Remove)?) .set_id(generate_activity_id(RemoveType::Remove)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
// Undo that fake activity // Undo that fake activity
let mut undo = Undo::new( let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
mod_.actor_id.to_owned().into_inner(),
remove.into_any_base()?,
);
undo undo
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?) .set_id(generate_activity_id(UndoType::Undo)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(undo, mod_, &community, context).await?; send_to_community(undo, mod_, &community, context).await?;
Ok(()) Ok(())
@ -194,43 +167,37 @@ impl ApubObjectType for Post {
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ApubLikeableType for Post { impl ApubLikeableType for Post {
async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id) Community::read(conn, community_id)
}) })
.await??; .await??;
let mut like = Like::new( let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
like like
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(LikeType::Like)?) .set_id(generate_activity_id(LikeType::Like)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(like, &creator, &community, context).await?; send_to_community(like, &creator, &community, context).await?;
Ok(()) Ok(())
} }
async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id) Community::read(conn, community_id)
}) })
.await??; .await??;
let mut dislike = Dislike::new( let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
dislike dislike
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DislikeType::Dislike)?) .set_id(generate_activity_id(DislikeType::Dislike)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(dislike, &creator, &community, context).await?; send_to_community(dislike, &creator, &community, context).await?;
Ok(()) Ok(())
@ -238,7 +205,7 @@ impl ApubLikeableType for Post {
async fn send_undo_like( async fn send_undo_like(
&self, &self,
creator: &Person, creator: &User_,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
@ -247,26 +214,20 @@ impl ApubLikeableType for Post {
}) })
.await??; .await??;
let mut like = Like::new( let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
creator.actor_id.to_owned().into_inner(),
self.ap_id.to_owned().into_inner(),
);
like like
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(LikeType::Like)?) .set_id(generate_activity_id(LikeType::Like)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
// Undo that fake activity // Undo that fake activity
let mut undo = Undo::new( let mut undo = Undo::new(creator.actor_id.to_owned(), like.into_any_base()?);
creator.actor_id.to_owned().into_inner(),
like.into_any_base()?,
);
undo undo
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?) .set_id(generate_activity_id(UndoType::Undo)?)
.set_to(public()) .set_to(public())
.set_many_ccs(vec![community.actor_id()]); .set_many_ccs(vec![community.actor_id()?]);
send_to_community(undo, &creator, &community, context).await?; send_to_community(undo, &creator, &community, context).await?;
Ok(()) Ok(())

View file

@ -0,0 +1,113 @@
use crate::{
activities::send::generate_activity_id,
activity_queue::send_activity_single_dest,
extensions::context::lemmy_context,
objects::ToApub,
ActorType,
ApubObjectType,
};
use activitystreams::{
activity::{
kind::{CreateType, DeleteType, UndoType, UpdateType},
Create,
Delete,
Undo,
Update,
},
prelude::*,
};
use lemmy_db_queries::Crud;
use lemmy_db_schema::source::{private_message::PrivateMessage, user::User_};
use lemmy_structs::blocking;
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
#[async_trait::async_trait(?Send)]
impl ApubObjectType for PrivateMessage {
/// Send out information about a newly created private message
async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?;
let recipient_id = self.recipient_id;
let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
let mut create = Create::new(creator.actor_id.to_owned(), note.into_any_base()?);
create
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(CreateType::Create)?)
.set_to(recipient.actor_id()?);
send_activity_single_dest(create, creator, recipient.get_inbox_url()?, context).await?;
Ok(())
}
/// Send out information about an edited private message, to the followers of the community.
async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?;
let recipient_id = self.recipient_id;
let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
let mut update = Update::new(creator.actor_id.to_owned(), note.into_any_base()?);
update
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UpdateType::Update)?)
.set_to(recipient.actor_id()?);
send_activity_single_dest(update, creator, recipient.get_inbox_url()?, context).await?;
Ok(())
}
async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let recipient_id = self.recipient_id;
let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
delete
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(recipient.actor_id()?);
send_activity_single_dest(delete, creator, recipient.get_inbox_url()?, context).await?;
Ok(())
}
async fn send_undo_delete(
&self,
creator: &User_,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let recipient_id = self.recipient_id;
let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
delete
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(recipient.actor_id()?);
// Undo that fake activity
let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
undo
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?)
.set_to(recipient.actor_id()?);
send_activity_single_dest(undo, creator, recipient.get_inbox_url()?, context).await?;
Ok(())
}
async fn send_remove(&self, _mod_: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
unimplemented!()
}
async fn send_undo_remove(
&self,
_mod_: &User_,
_context: &LemmyContext,
) -> Result<(), LemmyError> {
unimplemented!()
}
}

View file

@ -13,23 +13,20 @@ use activitystreams::{
base::{AnyBase, BaseExt, ExtendsExt}, base::{AnyBase, BaseExt, ExtendsExt},
object::ObjectExt, object::ObjectExt,
}; };
use lemmy_api_structs::blocking;
use lemmy_db_queries::{ApubObject, DbPool, Followable}; use lemmy_db_queries::{ApubObject, DbPool, Followable};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
community::{Community, CommunityFollower, CommunityFollowerForm}, community::{Community, CommunityFollower, CommunityFollowerForm},
person::Person, user::User_,
}; };
use lemmy_structs::blocking;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use url::Url; use url::Url;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ActorType for Person { impl ActorType for User_ {
fn is_local(&self) -> bool { fn actor_id_str(&self) -> String {
self.local self.actor_id.to_owned()
}
fn actor_id(&self) -> Url {
self.actor_id.to_owned().into_inner()
} }
fn public_key(&self) -> Option<String> { fn public_key(&self) -> Option<String> {
@ -40,29 +37,21 @@ impl ActorType for Person {
self.private_key.to_owned() self.private_key.to_owned()
} }
fn get_shared_inbox_or_inbox_url(&self) -> Url { /// As a given local user, send out a follow request to a remote community.
self
.shared_inbox_url
.clone()
.unwrap_or_else(|| self.inbox_url.to_owned())
.into()
}
/// As a given local person, send out a follow request to a remote community.
async fn send_follow( async fn send_follow(
&self, &self,
follow_actor_id: &Url, follow_actor_id: &Url,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let follow_actor_id = follow_actor_id.to_owned(); let follow_actor_id = follow_actor_id.to_string();
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &follow_actor_id.into()) Community::read_from_apub_id(conn, &follow_actor_id)
}) })
.await??; .await??;
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: community.id, community_id: community.id,
person_id: self.id, user_id: self.id,
pending: true, pending: true,
}; };
blocking(&context.pool(), move |conn| { blocking(&context.pool(), move |conn| {
@ -70,13 +59,13 @@ impl ActorType for Person {
}) })
.await?; .await?;
let mut follow = Follow::new(self.actor_id.to_owned().into_inner(), community.actor_id()); let mut follow = Follow::new(self.actor_id.to_owned(), community.actor_id()?);
follow follow
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(FollowType::Follow)?) .set_id(generate_activity_id(FollowType::Follow)?)
.set_to(community.actor_id()); .set_to(community.actor_id()?);
send_activity_single_dest(follow, self, community.inbox_url.into(), context).await?; send_activity_single_dest(follow, self, community.get_inbox_url()?, context).await?;
Ok(()) Ok(())
} }
@ -85,29 +74,26 @@ impl ActorType for Person {
follow_actor_id: &Url, follow_actor_id: &Url,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let follow_actor_id = follow_actor_id.to_owned(); let follow_actor_id = follow_actor_id.to_string();
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &follow_actor_id.into()) Community::read_from_apub_id(conn, &follow_actor_id)
}) })
.await??; .await??;
let mut follow = Follow::new(self.actor_id.to_owned().into_inner(), community.actor_id()); let mut follow = Follow::new(self.actor_id.to_owned(), community.actor_id()?);
follow follow
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(FollowType::Follow)?) .set_id(generate_activity_id(FollowType::Follow)?)
.set_to(community.actor_id()); .set_to(community.actor_id()?);
// Undo that fake activity // Undo that fake activity
let mut undo = Undo::new( let mut undo = Undo::new(Url::parse(&self.actor_id)?, follow.into_any_base()?);
self.actor_id.to_owned().into_inner(),
follow.into_any_base()?,
);
undo undo
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?) .set_id(generate_activity_id(UndoType::Undo)?)
.set_to(community.actor_id()); .set_to(community.actor_id()?);
send_activity_single_dest(undo, self, community.inbox_url.into(), context).await?; send_activity_single_dest(undo, self, community.get_inbox_url()?, context).await?;
Ok(()) Ok(())
} }

View file

@ -21,13 +21,13 @@ use background_jobs::{
}; };
use itertools::Itertools; use itertools::Itertools;
use lemmy_db_queries::DbPool; use lemmy_db_queries::DbPool;
use lemmy_db_schema::source::{community::Community, person::Person}; use lemmy_db_schema::source::{community::Community, user::User_};
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError}; use lemmy_utils::{location_info, settings::Settings, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use log::{debug, warn}; use log::{debug, warn};
use reqwest::Client; use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{export::fmt::Debug, Deserialize, Serialize};
use std::{collections::BTreeMap, env, fmt::Debug, future::Future, pin::Pin}; use std::{collections::BTreeMap, env, future::Future, pin::Pin};
use url::Url; use url::Url;
/// Sends a local activity to a single, remote actor. /// Sends a local activity to a single, remote actor.
@ -88,13 +88,13 @@ where
.await? .await?
.iter() .iter()
.unique() .unique()
.filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname())) .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname))
.filter(|inbox| check_is_apub_id_valid(inbox).is_ok()) .filter(|inbox| check_is_apub_id_valid(inbox).is_ok())
.map(|inbox| inbox.to_owned()) .map(|inbox| inbox.to_owned())
.collect(); .collect();
debug!( debug!(
"Sending activity {:?} to followers of {}", "Sending activity {:?} to followers of {}",
&activity.id_unchecked().map(|i| i.to_string()), &activity.id_unchecked(),
&community.actor_id &community.actor_id
); );
@ -112,7 +112,7 @@ where
Ok(()) Ok(())
} }
/// Sends an activity from a local person to a remote community. /// Sends an activity from a local user to a remote community.
/// ///
/// * `activity` the activity to send /// * `activity` the activity to send
/// * `creator` the creator of the activity /// * `creator` the creator of the activity
@ -120,7 +120,7 @@ where
/// ///
pub(crate) async fn send_to_community<T, Kind>( pub(crate) async fn send_to_community<T, Kind>(
activity: T, activity: T,
creator: &Person, creator: &User_,
community: &Community, community: &Community,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> ) -> Result<(), LemmyError>
@ -135,7 +135,7 @@ where
.send_announce(activity.into_any_base()?, context) .send_announce(activity.into_any_base()?, context)
.await?; .await?;
} else { } else {
let inbox = community.get_shared_inbox_or_inbox_url(); let inbox = community.get_shared_inbox_url()?;
check_is_apub_id_valid(&inbox)?; check_is_apub_id_valid(&inbox)?;
debug!( debug!(
"Sending activity {:?} to community {}", "Sending activity {:?} to community {}",
@ -157,13 +157,13 @@ where
Ok(()) Ok(())
} }
/// Sends notification to any persons mentioned in a comment /// Sends notification to any users mentioned in a comment
/// ///
/// * `creator` person who created the comment /// * `creator` user who created the comment
/// * `mentions` list of inboxes of persons which are mentioned in the comment /// * `mentions` list of inboxes of users which are mentioned in the comment
/// * `activity` either a `Create/Note` or `Update/Note` /// * `activity` either a `Create/Note` or `Update/Note`
pub(crate) async fn send_comment_mentions<T, Kind>( pub(crate) async fn send_comment_mentions<T, Kind>(
creator: &Person, creator: &User_,
mentions: Vec<Url>, mentions: Vec<Url>,
activity: T, activity: T,
context: &LemmyContext, context: &LemmyContext,
@ -215,17 +215,10 @@ where
Kind: Serialize, Kind: Serialize,
<T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static, <T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
{ {
if !Settings::get().federation().enabled || inboxes.is_empty() { if !Settings::get().federation.enabled || inboxes.is_empty() {
return Ok(()); return Ok(());
} }
// Don't send anything to ourselves
let hostname = Settings::get().get_hostname_without_port()?;
let inboxes: Vec<&Url> = inboxes
.iter()
.filter(|i| i.domain().expect("valid inbox url") != hostname)
.collect();
let activity = activity.into_any_base()?; let activity = activity.into_any_base()?;
let serialised_activity = serde_json::to_string(&activity)?; let serialised_activity = serde_json::to_string(&activity)?;
@ -239,8 +232,8 @@ where
for i in inboxes { for i in inboxes {
let message = SendActivityTask { let message = SendActivityTask {
activity: serialised_activity.to_owned(), activity: serialised_activity.to_owned(),
inbox: i.to_owned(), inbox: i,
actor_id: actor.actor_id(), actor_id: actor.actor_id()?,
private_key: actor.private_key().context(location_info!())?, private_key: actor.private_key().context(location_info!())?,
}; };
if env::var("LEMMY_TEST_SEND_SYNC").is_ok() { if env::var("LEMMY_TEST_SEND_SYNC").is_ok() {

View file

@ -6,6 +6,7 @@ pub(crate) fn lemmy_context() -> Result<Vec<AnyBase>, LemmyError> {
let context_ext = AnyBase::from_arbitrary_json(json!( let context_ext = AnyBase::from_arbitrary_json(json!(
{ {
"sc": "http://schema.org#", "sc": "http://schema.org#",
"category": "sc:category",
"sensitive": "as:sensitive", "sensitive": "as:sensitive",
"stickied": "as:stickied", "stickied": "as:stickied",
"comments_enabled": { "comments_enabled": {

View file

@ -1,20 +1,42 @@
use activitystreams::unparsed::UnparsedMutExt; use activitystreams::unparsed::UnparsedMutExt;
use activitystreams_ext::UnparsedExtension; use activitystreams_ext::UnparsedExtension;
use diesel::PgConnection;
use lemmy_db_queries::Crud;
use lemmy_db_schema::source::category::Category;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Activitystreams extension to allow (de)serializing additional Community field /// Activitystreams extension to allow (de)serializing additional Community fields `category` and
/// `sensitive` (called 'nsfw' in Lemmy). /// `sensitive` (called 'nsfw' in Lemmy).
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GroupExtension { pub struct GroupExtension {
pub sensitive: Option<bool>, pub category: GroupCategory,
pub sensitive: bool,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GroupCategory {
// Using a string because that's how Peertube does it.
pub identifier: String,
pub name: String,
} }
impl GroupExtension { impl GroupExtension {
pub fn new(sensitive: bool) -> Result<GroupExtension, LemmyError> { pub fn new(
conn: &PgConnection,
category_id: i32,
sensitive: bool,
) -> Result<GroupExtension, LemmyError> {
let category = Category::read(conn, category_id)?;
let group_category = GroupCategory {
identifier: category_id.to_string(),
name: category.name,
};
Ok(GroupExtension { Ok(GroupExtension {
sensitive: Some(sensitive), category: group_category,
sensitive,
}) })
} }
} }
@ -27,11 +49,13 @@ where
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> { fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
Ok(GroupExtension { Ok(GroupExtension {
category: unparsed_mut.remove("category")?,
sensitive: unparsed_mut.remove("sensitive")?, sensitive: unparsed_mut.remove("sensitive")?,
}) })
} }
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> { fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
unparsed_mut.insert("category", self.category)?;
unparsed_mut.insert("sensitive", self.sensitive)?; unparsed_mut.insert("sensitive", self.sensitive)?;
Ok(()) Ok(())
} }

View file

@ -8,9 +8,9 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PageExtension { pub struct PageExtension {
pub comments_enabled: Option<bool>, pub comments_enabled: bool,
pub sensitive: Option<bool>, pub sensitive: bool,
pub stickied: Option<bool>, pub stickied: bool,
} }
impl<U> UnparsedExtension<U> for PageExtension impl<U> UnparsedExtension<U> for PageExtension

View file

@ -95,20 +95,20 @@ pub(crate) fn verify_signature(
} }
} }
/// Extension for actor public key, which is needed on person and community for HTTP signatures. /// Extension for actor public key, which is needed on user and community for HTTP signatures.
/// ///
/// Taken from: https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html /// Taken from: https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PublicKeyExtension { pub struct PublicKeyExtension {
pub public_key: PublicKey, pub public_key: PublicKey,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PublicKey { pub struct PublicKey {
pub id: String, pub id: String,
pub owner: Url, pub owner: String,
pub public_key_pem: String, pub public_key_pem: String,
} }

View file

@ -1,24 +1,29 @@
use crate::{ use crate::{
check_is_apub_id_valid,
fetcher::{ fetcher::{
fetch::fetch_remote_object, fetch::fetch_remote_object,
get_or_fetch_and_upsert_person, get_or_fetch_and_upsert_user,
is_deleted, is_deleted,
should_refetch_actor, should_refetch_actor,
}, },
inbox::person_inbox::receive_announce,
objects::FromApub, objects::FromApub,
ActorType,
GroupExt, GroupExt,
PageExt,
}; };
use activitystreams::{ use activitystreams::{
actor::ApActorExt, base::{BaseExt, ExtendsExt},
collection::{CollectionExt, OrderedCollection}, collection::{CollectionExt, OrderedCollection},
object::ObjectExt, object::ObjectExt,
}; };
use anyhow::Context; use anyhow::Context;
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking;
use lemmy_db_queries::{source::community::Community_, ApubObject, Joinable}; use lemmy_db_queries::{source::community::Community_, ApubObject, Joinable};
use lemmy_db_schema::source::community::{Community, CommunityModerator, CommunityModeratorForm}; use lemmy_db_schema::source::{
community::{Community, CommunityModerator, CommunityModeratorForm},
post::Post,
};
use lemmy_structs::blocking;
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use log::debug; use log::debug;
@ -35,7 +40,7 @@ pub(crate) async fn get_or_fetch_and_upsert_community(
) -> Result<Community, LemmyError> { ) -> Result<Community, LemmyError> {
let apub_id_owned = apub_id.to_owned(); let apub_id_owned = apub_id.to_owned();
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &apub_id_owned.into()) Community::read_from_apub_id(conn, apub_id_owned.as_str())
}) })
.await?; .await?;
@ -92,7 +97,7 @@ async fn fetch_remote_community(
let mut creator_and_moderators = Vec::new(); let mut creator_and_moderators = Vec::new();
for uri in creator_and_moderator_uris { for uri in creator_and_moderator_uris {
let c_or_m = get_or_fetch_and_upsert_person(uri, context, recursion_counter).await?; let c_or_m = get_or_fetch_and_upsert_user(uri, context, recursion_counter).await?;
creator_and_moderators.push(c_or_m); creator_and_moderators.push(c_or_m);
} }
@ -104,7 +109,7 @@ async fn fetch_remote_community(
for mod_ in creator_and_moderators { for mod_ in creator_and_moderators {
let community_moderator_form = CommunityModeratorForm { let community_moderator_form = CommunityModeratorForm {
community_id, community_id,
person_id: mod_.id, user_id: mod_.id,
}; };
CommunityModerator::join(conn, &community_moderator_form)?; CommunityModerator::join(conn, &community_moderator_form)?;
@ -114,32 +119,29 @@ async fn fetch_remote_community(
.await??; .await??;
} }
// only fetch outbox for new communities, otherwise this can create an infinite loop // fetch outbox (maybe make this conditional)
if old_community.is_none() { let outbox = fetch_remote_object::<OrderedCollection>(
let outbox = group.inner.outbox()?.context(location_info!())?; context.client(),
fetch_community_outbox(context, outbox, &community, recursion_counter).await? &community.get_outbox_url()?,
recursion_counter,
)
.await?;
let outbox_items = outbox.items().context(location_info!())?.clone();
let mut outbox_items = outbox_items.many().context(location_info!())?;
if outbox_items.len() > 20 {
outbox_items = outbox_items[0..20].to_vec();
}
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 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
} }
Ok(community) Ok(community)
} }
async fn fetch_community_outbox(
context: &LemmyContext,
outbox: &Url,
community: &Community,
recursion_counter: &mut i32,
) -> Result<(), LemmyError> {
let outbox =
fetch_remote_object::<OrderedCollection>(context.client(), outbox, recursion_counter).await?;
let outbox_activities = outbox.items().context(location_info!())?.clone();
let mut outbox_activities = outbox_activities.many().context(location_info!())?;
if outbox_activities.len() > 20 {
outbox_activities = outbox_activities[0..20].to_vec();
}
for activity in outbox_activities {
receive_announce(context, activity, community, recursion_counter).await?;
}
Ok(())
}

View file

@ -10,9 +10,8 @@ use url::Url;
/// Maximum number of HTTP requests allowed to handle a single incoming activity (or a single object /// Maximum number of HTTP requests allowed to handle a single incoming activity (or a single object
/// fetch through the search). /// fetch through the search).
/// ///
/// A community fetch will load the outbox with up to 20 items, and fetch the creator for each item. /// Tests are passing with a value of 5, so 10 should be safe for production.
/// So we are looking at a maximum of 22 requests (rounded up just to be safe). static MAX_REQUEST_NUMBER: i32 = 10;
static MAX_REQUEST_NUMBER: i32 = 25;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub(in crate::fetcher) struct FetchError { pub(in crate::fetcher) struct FetchError {

View file

@ -1,14 +1,14 @@
pub(crate) mod community; pub(crate) mod community;
mod fetch; mod fetch;
pub(crate) mod objects; pub(crate) mod objects;
pub(crate) mod person;
pub mod search; pub mod search;
pub(crate) mod user;
use crate::{ use crate::{
fetcher::{ fetcher::{
community::get_or_fetch_and_upsert_community, community::get_or_fetch_and_upsert_community,
fetch::FetchError, fetch::FetchError,
person::get_or_fetch_and_upsert_person, user::get_or_fetch_and_upsert_user,
}, },
ActorType, ActorType,
}; };
@ -37,8 +37,8 @@ where
false false
} }
/// Get a remote actor from its apub ID (either a person or a community). Thin wrapper around /// Get a remote actor from its apub ID (either a user or a community). Thin wrapper around
/// `get_or_fetch_and_upsert_person()` and `get_or_fetch_and_upsert_community()`. /// `get_or_fetch_and_upsert_user()` and `get_or_fetch_and_upsert_community()`.
/// ///
/// If it exists locally and `!should_refetch_actor()`, it is returned directly from the database. /// If it exists locally and `!should_refetch_actor()`, it is returned directly from the database.
/// Otherwise it is fetched from the remote instance, stored and returned. /// Otherwise it is fetched from the remote instance, stored and returned.
@ -50,7 +50,7 @@ pub(crate) async fn get_or_fetch_and_upsert_actor(
let community = get_or_fetch_and_upsert_community(apub_id, context, recursion_counter).await; let community = get_or_fetch_and_upsert_community(apub_id, context, recursion_counter).await;
let actor: Box<dyn ActorType> = match community { let actor: Box<dyn ActorType> = match community {
Ok(c) => Box::new(c), Ok(c) => Box::new(c),
Err(_) => Box::new(get_or_fetch_and_upsert_person(apub_id, context, recursion_counter).await?), Err(_) => Box::new(get_or_fetch_and_upsert_user(apub_id, context, recursion_counter).await?),
}; };
Ok(actor) Ok(actor)
} }

Some files were not shown because too many files have changed in this diff Show more