Compare commits
185 commits
crates-fol
...
main
Author | SHA1 | Date | |
---|---|---|---|
f55ef1d7ef | |||
14bc9f0946 | |||
493598c1ba | |||
96efe302ce | |||
e25bcb35d7 | |||
05b485b678 | |||
360d4ea8d1 | |||
c06d612432 | |||
c88722983e | |||
33a326854a | |||
9930c7288a | |||
8d9fab0389 | |||
c3efb9f7cf | |||
5899b89ef2 | |||
99e5a4d1c3 | |||
|
db4fe8031c | ||
270ce539bf | |||
b9f483bc27 | |||
8ee624a542 | |||
621355b6ef | |||
72b5e0cab5 | |||
|
74272ed754 | ||
|
4426c3176d | ||
|
7b0a09e84e | ||
|
ab947f1f08 | ||
5998c83b2a | |||
434fb53dd1 | |||
75a95acf04 | |||
|
931a132161 | ||
0a7271a185 | |||
7c039340ed | |||
7d04f371a5 | |||
5d8ccbafe4 | |||
1a4c8c08ee | |||
9cb4dad4b4 | |||
ddf4a667b1 | |||
|
fc74bfeb23 | ||
8f6b8895f4 | |||
|
a650312858 | ||
ff2c71a74a | |||
|
126c6a23bb | ||
e0c61c1334 | |||
f7aa97d45e | |||
a1c7584875 | |||
817b4ff08e | |||
ca3c1269f5 | |||
|
0a52396706 | ||
|
7c4969c92b | ||
|
45a94203f2 | ||
7189328f80 | |||
|
134fece36d | ||
985dbcaada | |||
|
e78ba38e94 | ||
7f56281c26 | |||
|
45e05dac30 | ||
66946117e1 | |||
|
462c4a2954 | ||
|
5ce8adcb13 | ||
3bdd78f341 | |||
|
b5aa4cf41a | ||
|
a5e2463097 | ||
a869a2823b | |||
|
ff3e26452a | ||
|
da5b27ecc6 | ||
c618b4efaa | |||
4cc341e4aa | |||
82d97cf6de | |||
|
600ae662a5 | ||
efc9047f87 | |||
aba32917bd | |||
ea3c0e1772 | |||
058052b46c | |||
|
7c87da012e | ||
bf1e859e72 | |||
289cef3101 | |||
085c307b8b | |||
72783edb17 | |||
3141ad31de | |||
|
40ceec9737 | ||
723ec65ac6 | |||
3ae62573b7 | |||
|
6499709221 | ||
813fcabe13 | |||
92ea9b97dd | |||
|
0c9b109bf7 | ||
|
2cbd158a11 | ||
|
7dc3ff4544 | ||
8d5e9f865c | |||
8096765f0e | |||
8eb81bb153 | |||
c81435c994 | |||
7548b44d1b | |||
bcc8dae16b | |||
b014ac44c8 | |||
a806493bc2 | |||
b593047fb1 | |||
9845366a36 | |||
|
15d0f54a88 | ||
|
8088055d38 | ||
|
0c4b57a6d0 | ||
d3707ad4ef | |||
13a949d9ec | |||
6f683682c3 | |||
a920bf768e | |||
a183815870 | |||
d0bd02eea0 | |||
37ea778776 | |||
|
f37fd0ecfd | ||
3d400ca21d | |||
71aa8f3670 | |||
37ad9e9a09 | |||
1af906c224 | |||
|
f899831ed3 | ||
08900b5b94 | |||
5a33fce8bd | |||
acadf0289e | |||
2e5ccaf7fe | |||
63d9c0ee46 | |||
|
68edda7bf5 | ||
999d9f4d6c | |||
ce00677880 | |||
5656db3e3d | |||
c213edf7ee | |||
a7540b4947 | |||
5f112aad44 | |||
f198f281cf | |||
bf751dc7ab | |||
|
6f364e60fa | ||
2b6df63aee | |||
|
def8af7d8a | ||
14465b91b1 | |||
d09df9c02b | |||
897263e5a3 | |||
4864f80656 | |||
105dfc93f1 | |||
|
d5d99fa3b9 | ||
8a7e50381f | |||
f45f2ec202 | |||
|
1a4e35eb50 | ||
ed8a12f96f | |||
525fdcf73c | |||
840ab7cba4 | |||
9415bec557 | |||
712d497a2d | |||
dbc94af51d | |||
86d8c9b18e | |||
1857f02af8 | |||
10f0b3b877 | |||
|
0be9b5bddb | ||
|
6bb4f0b41f | ||
|
f4d33389a5 | ||
c8254dc0a8 | |||
6b36bf772e | |||
d2ba2960dd | |||
cd08fdf76f | |||
aecb2411d8 | |||
9609bd99bb | |||
|
5102cdddc1 | ||
3a05817b41 | |||
51465bc0d7 | |||
2322534648 | |||
3f23e0e6b9 | |||
e6a16f08a3 | |||
|
0fd0279543 | ||
|
f5e58c8bf5 | ||
5f59d7ba5f | |||
62a145d8b3 | |||
c09c462a6e | |||
3d578f9df2 | |||
363ceea5c7 | |||
c51f750831 | |||
|
cf911c023d | ||
ea59cf16e8 | |||
91d210ce79 | |||
f0dcc3a104 | |||
b2d6b554e8 | |||
1addbe361a | |||
|
97617d699d | ||
ac969dc737 | |||
c014bef84d | |||
f2f9f5776c | |||
cb9c354c28 | |||
|
b1fb90ff6d | ||
|
ee03cf8ae9 | ||
|
a01af67948 |
230 changed files with 8693 additions and 6144 deletions
73
.drone.yml
73
.drone.yml
|
@ -7,14 +7,9 @@ platform:
|
|||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: fetch git submodules
|
||||
image: node:15-alpine3.12
|
||||
commands:
|
||||
- apk add git
|
||||
- git submodule update --init --recursive --remote
|
||||
|
||||
- name: chown repo
|
||||
image: ekidd/rust-musl-builder:1.47.0
|
||||
image: ekidd/rust-musl-builder:1.50.0
|
||||
user: root
|
||||
commands:
|
||||
- chown 1000:1000 . -R
|
||||
|
@ -25,23 +20,33 @@ steps:
|
|||
- /root/.cargo/bin/cargo fmt -- --check
|
||||
|
||||
- name: cargo clippy
|
||||
image: ekidd/rust-musl-builder:1.47.0
|
||||
image: ekidd/rust-musl-builder:1.50.0
|
||||
environment:
|
||||
CARGO_HOME: /drone/src/.cargo
|
||||
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 -- -D clippy::unwrap_used
|
||||
|
||||
- name: cargo test
|
||||
image: ekidd/rust-musl-builder:1.47.0
|
||||
image: ekidd/rust-musl-builder:1.50.0
|
||||
environment:
|
||||
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||
RUST_BACKTRACE: 1
|
||||
RUST_TEST_THREADS: 1
|
||||
CARGO_HOME: /drone/src/.cargo
|
||||
commands:
|
||||
- sudo apt-get update
|
||||
- sudo apt-get -y install --no-install-recommends espeak postgresql-client
|
||||
- cargo test --workspace --no-fail-fast
|
||||
|
||||
- name: cargo build
|
||||
image: ekidd/rust-musl-builder:1.47.0
|
||||
image: ekidd/rust-musl-builder:1.50.0
|
||||
environment:
|
||||
CARGO_HOME: /drone/src/.cargo
|
||||
commands:
|
||||
- cargo build
|
||||
- mv target/x86_64-unknown-linux-musl/debug/lemmy_server target/lemmy_server
|
||||
|
@ -68,6 +73,24 @@ steps:
|
|||
from_secret: docker_password
|
||||
repo: dessalines/lemmy
|
||||
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:
|
||||
ref:
|
||||
- refs/tags/*
|
||||
|
@ -89,18 +112,13 @@ platform:
|
|||
|
||||
steps:
|
||||
|
||||
- name: fetch git submodules
|
||||
image: node:15-alpine3.12
|
||||
commands:
|
||||
- apk add git
|
||||
- git submodule update --init --recursive --remote
|
||||
|
||||
- name: cargo test
|
||||
image: rust:1.47-slim-buster
|
||||
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
|
||||
|
@ -109,7 +127,9 @@ steps:
|
|||
|
||||
# Using Debian here because there seems to be no official Alpine-based Rust docker image for ARM.
|
||||
- name: cargo build
|
||||
image: rust:1.47-slim-buster
|
||||
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
|
||||
|
@ -140,7 +160,24 @@ steps:
|
|||
from_secret: docker_password
|
||||
repo: dessalines/lemmy
|
||||
auto_tag: true
|
||||
auto_tag_suffix: arm64
|
||||
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/*
|
||||
|
|
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -1,4 +0,0 @@
|
|||
[submodule "docs"]
|
||||
path = docs
|
||||
url = https://github.com/LemmyNet/lemmy-docs
|
||||
branch = main
|
1
.rgignore
Normal file
1
.rgignore
Normal file
|
@ -0,0 +1 @@
|
|||
*.sqldump
|
|
@ -1,5 +1,5 @@
|
|||
tab_spaces = 2
|
||||
edition="2018"
|
||||
imports_layout="HorizontalVertical"
|
||||
merge_imports=true
|
||||
imports_granularity="Crate"
|
||||
reorder_imports=true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Contributing
|
||||
|
||||
See [here](https://lemmy.ml/docs/contributing.html) for contributing Instructions.
|
||||
See [here](https://join.lemmy.ml/docs/en/contributing/contributing.html) for contributing Instructions.
|
||||
|
||||
|
|
1080
Cargo.lock
generated
1080
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
30
Cargo.toml
30
Cargo.toml
|
@ -3,6 +3,9 @@ name = "lemmy_server"
|
|||
version = "0.0.1"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
|
||||
|
@ -16,8 +19,9 @@ members = [
|
|||
"crates/db_views",
|
||||
"crates/db_views_actor",
|
||||
"crates/db_views_actor",
|
||||
"crates/structs",
|
||||
"crates/api_structs",
|
||||
"crates/websocket",
|
||||
"crates/routes"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
|
@ -29,32 +33,28 @@ lemmy_db_queries = { path = "./crates/db_queries" }
|
|||
lemmy_db_views = { path = "./crates/db_views" }
|
||||
lemmy_db_views_moderator = { path = "./crates/db_views_moderator" }
|
||||
lemmy_db_views_actor = { path = "./crates/db_views_actor" }
|
||||
lemmy_structs = { path = "./crates/structs" }
|
||||
lemmy_api_structs = { path = "crates/api_structs" }
|
||||
lemmy_websocket = { path = "./crates/websocket" }
|
||||
lemmy_routes = { path = "./crates/routes" }
|
||||
diesel = "1.4.5"
|
||||
diesel_migrations = "1.4.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
serde = { version = "1.0.118", features = ["derive"] }
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
actix = "0.10.0"
|
||||
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
|
||||
actix-files = { version = "0.4.1", default-features = false }
|
||||
actix-web-actors = { version = "3.0.0", default-features = false }
|
||||
awc = { version = "2.0.3", default-features = false }
|
||||
log = "0.4.11"
|
||||
log = "0.4.14"
|
||||
env_logger = "0.8.2"
|
||||
strum = "0.20.0"
|
||||
lazy_static = "1.4.0"
|
||||
rss = "1.9.0"
|
||||
url = { version = "2.2.0", features = ["serde"] }
|
||||
openssl = "0.10.31"
|
||||
url = { version = "2.2.1", features = ["serde"] }
|
||||
openssl = "0.10.32"
|
||||
http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
|
||||
tokio = "0.3.6"
|
||||
sha2 = "0.9.2"
|
||||
anyhow = "1.0.36"
|
||||
anyhow = "1.0.38"
|
||||
reqwest = { version = "0.10.10", features = ["json"] }
|
||||
activitystreams = "0.7.0-alpha.8"
|
||||
activitystreams = "0.7.0-alpha.10"
|
||||
actix-rt = { version = "1.1.1", default-features = false }
|
||||
serde_json = { version = "1.0.60", features = ["preserve_order"] }
|
||||
serde_json = { version = "1.0.61", features = ["preserve_order"] }
|
||||
clokwerk = "0.3.4"
|
||||
|
||||
[dev-dependencies.cargo-husky]
|
||||
version = "1.5.0"
|
||||
|
|
31
README.md
31
README.md
|
@ -1,12 +1,13 @@
|
|||
<div align="center">
|
||||
|
||||
![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)
|
||||
[![Build Status](https://cloud.drone.io/api/badges/LemmyNet/lemmy/status.svg)](https://cloud.drone.io/LemmyNet/lemmy/)
|
||||
[![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/)
|
||||
[![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)
|
||||
![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>
|
||||
|
||||
<p align="center">
|
||||
|
@ -20,7 +21,7 @@
|
|||
<br />
|
||||
<a href="https://join.lemmy.ml">Join Lemmy</a>
|
||||
·
|
||||
<a href="https://lemmy.ml/docs/index.html">Documentation</a>
|
||||
<a href="https://join.lemmy.ml/docs/en/index.html">Documentation</a>
|
||||
·
|
||||
<a href="https://github.com/LemmyNet/lemmy/issues">Report Bug</a>
|
||||
·
|
||||
|
@ -28,15 +29,15 @@
|
|||
·
|
||||
<a href="https://github.com/LemmyNet/lemmy/blob/main/RELEASES.md">Releases</a>
|
||||
·
|
||||
<a href="https://lemmy.ml/docs/en/code_of_conduct.html">Code of Conduct</a>
|
||||
<a href="https://join.lemmy.ml/docs/en/code_of_conduct.html">Code of Conduct</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
## About The Project
|
||||
|
||||
Front Page|Post
|
||||
Desktop|Mobile
|
||||
---|---
|
||||
![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)
|
||||
![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)
|
||||
|
||||
[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).
|
||||
|
||||
|
@ -46,7 +47,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.
|
||||
|
||||
*Note: Federation is still in active development and the WebSocket, as well as, HTTP API are currently unstable*
|
||||
*Note: The WebSocket and HTTP APIs are currently unstable*
|
||||
|
||||
### Why's it called Lemmy?
|
||||
|
||||
|
@ -67,7 +68,7 @@ Each Lemmy server can set its own moderation policy; appointing site-wide admins
|
|||
|
||||
- Open source, [AGPL License](/LICENSE).
|
||||
- Self hostable, easy to deploy.
|
||||
- Comes with [Docker](https://lemmy.ml/docs/administration_install_docker.html) and [Ansible](https://lemmy.ml/docs/administration_install_ansible.html).
|
||||
- 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).
|
||||
- Clean, mobile-friendly interface.
|
||||
- Only a minimum of a username and password is required to sign up!
|
||||
- User avatar support.
|
||||
|
@ -102,16 +103,16 @@ Each Lemmy server can set its own moderation policy; appointing site-wide admins
|
|||
|
||||
## Installation
|
||||
|
||||
- [Docker](https://lemmy.ml/docs/administration_install_docker.html)
|
||||
- [Ansible](https://lemmy.ml/docs/administration_install_ansible.html)
|
||||
- [Docker](https://join.lemmy.ml/docs/en/administration/install_docker.html)
|
||||
- [Ansible](https://join.lemmy.ml/docs/en/administration/install_ansible.html)
|
||||
|
||||
## Lemmy Projects
|
||||
|
||||
### Apps
|
||||
|
||||
- [lemmy-ui - The official web app for lemmy](https://github.com/LemmyNet/lemmy-ui)
|
||||
- [Lemmur - A flutter lemmy app ( under development )](https://github.com/krawieck/lemmur)
|
||||
- [Lemmy-mobile (Android / IOS) - React native ( under development )](https://github.com/koredefashokun/lemmy-mobile)
|
||||
- [Lemmur - A mobile client for Lemmy (Android, Linux, Windows)](https://github.com/krawieck/lemmur)
|
||||
- [Remmel - A native iOS app](https://github.com/uuttff8/Lemmy-iOS)
|
||||
|
||||
### Libraries
|
||||
|
||||
|
@ -136,13 +137,13 @@ Lemmy is free, open-source software, meaning no advertising, monetizing, or vent
|
|||
|
||||
## Contributing
|
||||
|
||||
- [Contributing instructions](https://lemmy.ml/docs/contributing.html)
|
||||
- [Docker Development](https://lemmy.ml/docs/contributing_docker_development.html)
|
||||
- [Local Development](https://lemmy.ml/docs/contributing_local_development.html)
|
||||
- [Contributing instructions](https://join.lemmy.ml/docs/en/contributing/contributing.html)
|
||||
- [Docker Development](https://join.lemmy.ml/docs/en/contributing/docker_development.html)
|
||||
- [Local Development](https://join.lemmy.ml/docs/en/contributing/local_development.html)
|
||||
|
||||
### Translations
|
||||
|
||||
If you want to help with translating, take a look at [Weblate](https://weblate.yerbamate.ml/projects/lemmy/).
|
||||
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).
|
||||
|
||||
## Contact
|
||||
|
||||
|
|
131
RELEASES.md
131
RELEASES.md
|
@ -1,3 +1,126 @@
|
|||
# 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)
|
||||
|
||||
## Changes
|
||||
|
@ -20,7 +143,7 @@ Here are some of the bigger changes:
|
|||
- The first **federation public beta release**, woohoo :fireworks:
|
||||
- All Lemmy functionality now works over ActivityPub (except turning remote users into mods/admins)
|
||||
- Instance allowlist and blocklist
|
||||
- Documentation for [admins](https://lemmy.ml/docs/administration_federation.html) and [devs](https://lemmy.ml/docs/contributing_federation_overview.html) on how federation works
|
||||
- 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
|
||||
- Upgraded to newest versions of @asonix activitypub libraries
|
||||
- Full local federation setup for manual testing
|
||||
- Automated testing for nearly every federation action
|
||||
|
@ -58,8 +181,8 @@ We'd also like to thank both the [NLnet foundation](https://nlnet.nl/) for their
|
|||
|
||||
## Upgrading
|
||||
|
||||
- [with manual Docker installation](https://lemmy.ml/docs/administration_install_docker.html#updating)
|
||||
- [with Ansible installation](https://lemmy.ml/docs/administration_install_ansible.html)
|
||||
- [with manual Docker installation](https://join.lemmy.ml/docs/administration_install_docker.html#updating)
|
||||
- [with Ansible installation](https://join.lemmy.ml/docs/administration_install_ansible.html)
|
||||
|
||||
## Testing Federation
|
||||
|
||||
|
@ -147,7 +270,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
|
||||
database and image files. See our
|
||||
[documentation](https://lemmy.ml/docs/administration_backup_and_restore.html)
|
||||
[documentation](https://join.lemmy.ml/docs/administration_backup_and_restore.html)
|
||||
for backup instructions.
|
||||
|
||||
**With Ansible:**
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.9.0-rc.12
|
||||
0.10.0-rc.7
|
||||
|
|
|
@ -64,6 +64,14 @@
|
|||
- src: '../docker/iframely.config.local.js'
|
||||
dest: '{{lemmy_base_dir}}/iframely.config.local.js'
|
||||
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)
|
||||
template:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
# for more info about the config, check out the documentation
|
||||
# https://lemmy.ml/docs/administration_configuration.html
|
||||
# https://join.lemmy.ml/docs/en/administration/configuration.html
|
||||
|
||||
# settings related to the postgresql database
|
||||
database: {
|
||||
|
@ -26,11 +26,12 @@
|
|||
# whether to enable activitypub federation.
|
||||
enabled: false
|
||||
# Allows and blocks are described here:
|
||||
# https://lemmy.ml/docs/administration_federation.html#instance-allowlist-and-blocklist
|
||||
# https://join.lemmy.ml/docs/en/federation/administration.html#instance-allowlist-and-blocklist
|
||||
#
|
||||
# comma separated list of instances with which federation is allowed
|
||||
# allowed_instances: ""
|
||||
# Only one of these blocks should be uncommented
|
||||
# allowed_instances: ["instance1.tld","instance2.tld"]
|
||||
# comma separated list of instances which are blocked from federating
|
||||
# blocked_instances: ""
|
||||
# blocked_instances: []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
version: '3.3'
|
||||
version: '2.2'
|
||||
|
||||
services:
|
||||
lemmy:
|
||||
|
@ -38,13 +38,14 @@ services:
|
|||
restart: always
|
||||
|
||||
pictrs:
|
||||
image: asonix/pictrs:v0.2.5-r0
|
||||
image: asonix/pictrs:v0.2.6-r1
|
||||
user: 991:991
|
||||
ports:
|
||||
- "127.0.0.1:8537:8080"
|
||||
volumes:
|
||||
- ./volumes/pictrs:/mnt
|
||||
restart: always
|
||||
mem_limit: 200m
|
||||
|
||||
iframely:
|
||||
image: dogbin/iframely:latest
|
||||
|
@ -53,6 +54,7 @@ services:
|
|||
volumes:
|
||||
- ./iframely.config.local.js:/iframely/config.local.js:ro
|
||||
restart: always
|
||||
mem_limit: 200m
|
||||
|
||||
postfix:
|
||||
image: mwader/postfix-relay
|
||||
|
|
|
@ -78,7 +78,7 @@ server {
|
|||
}
|
||||
|
||||
# backend
|
||||
location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
|
||||
location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) {
|
||||
proxy_pass http://0.0.0.0:{{ lemmy_port }};
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
"api-test": "jest src/ -i --verbose"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.19",
|
||||
"jest": "^26.6.3",
|
||||
"lemmy-js-client": "0.9.0-rc.12",
|
||||
"node-fetch": "^2.6.1",
|
||||
"ts-jest": "^26.4.4",
|
||||
"prettier": "^2.1.2",
|
||||
"eslint": "^7.10.0",
|
||||
"@types/jest": "^26.0.20",
|
||||
"eslint": "^7.18.0",
|
||||
"eslint-plugin-jane": "^9.0.3",
|
||||
"jest": "^26.6.3",
|
||||
"lemmy-js-client": "0.10.0-rc.4",
|
||||
"node-fetch": "^2.6.1",
|
||||
"prettier": "^2.1.2",
|
||||
"ts-jest": "^26.4.4",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
#!/bin/bash
|
||||
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 RUST_BACKTRACE=1
|
||||
|
||||
|
@ -35,52 +28,40 @@ fi
|
|||
|
||||
killall lemmy_server || true
|
||||
|
||||
echo "$PWD"
|
||||
|
||||
echo "start alpha"
|
||||
LEMMY_HOSTNAME=lemmy-alpha:8541 \
|
||||
LEMMY_PORT=8541 \
|
||||
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_alpha.hjson \
|
||||
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_alpha" \
|
||||
LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon \
|
||||
LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha \
|
||||
LEMMY_SETUP__SITE_NAME=lemmy-alpha \
|
||||
target/lemmy_server >/dev/null 2>&1 &
|
||||
LEMMY_HOSTNAME="lemmy-alpha:8541" \
|
||||
target/lemmy_server >/tmp/lemmy_alpha.out 2>&1 &
|
||||
|
||||
echo "start beta"
|
||||
LEMMY_HOSTNAME=lemmy-beta:8551 \
|
||||
LEMMY_PORT=8551 \
|
||||
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_beta.hjson \
|
||||
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_beta" \
|
||||
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 &
|
||||
target/lemmy_server >/tmp/lemmy_beta.out 2>&1 &
|
||||
|
||||
echo "start gamma"
|
||||
LEMMY_HOSTNAME=lemmy-gamma:8561 \
|
||||
LEMMY_PORT=8561 \
|
||||
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_gamma.hjson \
|
||||
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_gamma" \
|
||||
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 &
|
||||
target/lemmy_server >/tmp/lemmy_gamma.out 2>&1 &
|
||||
|
||||
echo "start delta"
|
||||
# An instance with only an allowlist for beta
|
||||
LEMMY_HOSTNAME=lemmy-delta:8571 \
|
||||
LEMMY_PORT=8571 \
|
||||
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_delta.hjson \
|
||||
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_delta" \
|
||||
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 &
|
||||
target/lemmy_server >/tmp/lemmy_delta.out 2>&1 &
|
||||
|
||||
echo "start epsilon"
|
||||
# An instance who has a blocklist, with lemmy-alpha blocked
|
||||
LEMMY_HOSTNAME=lemmy-epsilon:8581 \
|
||||
LEMMY_PORT=8581 \
|
||||
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_epsilon.hjson \
|
||||
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_epsilon" \
|
||||
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 &
|
||||
target/lemmy_server >/tmp/lemmy_epsilon.out 2>&1 &
|
||||
|
||||
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
|
||||
|
|
|
@ -4,7 +4,7 @@ set -e
|
|||
export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432
|
||||
|
||||
pushd ..
|
||||
cargo +1.47.0 build
|
||||
cargo build
|
||||
rm target/lemmy_server || true
|
||||
cp target/debug/lemmy_server target/lemmy_server
|
||||
./api_tests/prepare-drone-federation-test.sh
|
||||
|
|
|
@ -33,9 +33,6 @@ function assertCommunityFederation(
|
|||
);
|
||||
expect(communityOne.creator.actor_id).toBe(communityTwo.creator.actor_id);
|
||||
expect(communityOne.community.nsfw).toBe(communityTwo.community.nsfw);
|
||||
expect(communityOne.community.category_id).toBe(
|
||||
communityTwo.community.category_id
|
||||
);
|
||||
expect(communityOne.community.removed).toBe(communityTwo.community.removed);
|
||||
expect(communityOne.community.deleted).toBe(communityTwo.community.deleted);
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ import {
|
|||
getPost,
|
||||
unfollowRemotes,
|
||||
searchForUser,
|
||||
banUserFromSite,
|
||||
banPersonFromSite,
|
||||
searchPostLocal,
|
||||
banUserFromCommunity,
|
||||
banPersonFromCommunity,
|
||||
} from './shared';
|
||||
import { PostView, CommunityView } from 'lemmy-js-client';
|
||||
|
||||
|
@ -305,7 +305,7 @@ test('Enforce site ban for federated user', async () => {
|
|||
expect(alphaUser).toBeDefined();
|
||||
|
||||
// ban alpha from beta site
|
||||
let banAlpha = await banUserFromSite(beta, alphaUser.user.id, true);
|
||||
let banAlpha = await banPersonFromSite(beta, alphaUser.person.id, true);
|
||||
expect(banAlpha.banned).toBe(true);
|
||||
|
||||
// Alpha makes post on beta
|
||||
|
@ -321,7 +321,7 @@ test('Enforce site ban for federated user', async () => {
|
|||
expect(betaPost).toBeUndefined();
|
||||
|
||||
// Unban alpha
|
||||
let unBanAlpha = await banUserFromSite(beta, alphaUser.user.id, false);
|
||||
let unBanAlpha = await banPersonFromSite(beta, alphaUser.person.id, false);
|
||||
expect(unBanAlpha.banned).toBe(false);
|
||||
});
|
||||
|
||||
|
@ -332,8 +332,8 @@ test('Enforce community ban for federated user', async () => {
|
|||
expect(alphaUser).toBeDefined();
|
||||
|
||||
// ban alpha from beta site
|
||||
await banUserFromCommunity(beta, alphaUser.user.id, 2, false);
|
||||
let banAlpha = await banUserFromCommunity(beta, alphaUser.user.id, 2, true);
|
||||
await banPersonFromCommunity(beta, alphaUser.person.id, 2, false);
|
||||
let banAlpha = await banPersonFromCommunity(beta, alphaUser.person.id, 2, true);
|
||||
expect(banAlpha.banned).toBe(true);
|
||||
|
||||
// Alpha makes post on beta
|
||||
|
@ -349,9 +349,9 @@ test('Enforce community ban for federated user', async () => {
|
|||
expect(betaPost).toBeUndefined();
|
||||
|
||||
// Unban alpha
|
||||
let unBanAlpha = await banUserFromCommunity(
|
||||
let unBanAlpha = await banPersonFromCommunity(
|
||||
beta,
|
||||
alphaUser.user.id,
|
||||
alphaUser.person.id,
|
||||
2,
|
||||
false
|
||||
);
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
CreateCommunity,
|
||||
DeleteCommunity,
|
||||
RemoveCommunity,
|
||||
GetUserMentions,
|
||||
GetPersonMentions,
|
||||
CreateCommentLike,
|
||||
CreatePostLike,
|
||||
EditPrivateMessage,
|
||||
|
@ -36,15 +36,15 @@ import {
|
|||
GetPost,
|
||||
PrivateMessageResponse,
|
||||
PrivateMessagesResponse,
|
||||
GetUserMentionsResponse,
|
||||
GetPersonMentionsResponse,
|
||||
SaveUserSettings,
|
||||
SortType,
|
||||
ListingType,
|
||||
GetSiteResponse,
|
||||
SearchType,
|
||||
LemmyHttp,
|
||||
BanUserResponse,
|
||||
BanUser,
|
||||
BanPersonResponse,
|
||||
BanPerson,
|
||||
BanFromCommunity,
|
||||
BanFromCommunityResponse,
|
||||
Post,
|
||||
|
@ -289,32 +289,32 @@ export async function searchForUser(
|
|||
return api.client.search(form);
|
||||
}
|
||||
|
||||
export async function banUserFromSite(
|
||||
export async function banPersonFromSite(
|
||||
api: API,
|
||||
user_id: number,
|
||||
person_id: number,
|
||||
ban: boolean
|
||||
): Promise<BanUserResponse> {
|
||||
): Promise<BanPersonResponse> {
|
||||
// Make sure lemmy-beta/c/main is cached on lemmy_alpha
|
||||
// Use short-hand search url
|
||||
let form: BanUser = {
|
||||
user_id,
|
||||
let form: BanPerson = {
|
||||
person_id,
|
||||
ban,
|
||||
remove_data: false,
|
||||
auth: api.auth,
|
||||
};
|
||||
return api.client.banUser(form);
|
||||
return api.client.banPerson(form);
|
||||
}
|
||||
|
||||
export async function banUserFromCommunity(
|
||||
export async function banPersonFromCommunity(
|
||||
api: API,
|
||||
user_id: number,
|
||||
person_id: number,
|
||||
community_id: number,
|
||||
ban: boolean
|
||||
): Promise<BanFromCommunityResponse> {
|
||||
// Make sure lemmy-beta/c/main is cached on lemmy_alpha
|
||||
// Use short-hand search url
|
||||
let form: BanFromCommunity = {
|
||||
user_id,
|
||||
person_id,
|
||||
community_id,
|
||||
remove_data: false,
|
||||
ban,
|
||||
|
@ -413,13 +413,13 @@ export async function removeComment(
|
|||
return api.client.removeComment(form);
|
||||
}
|
||||
|
||||
export async function getMentions(api: API): Promise<GetUserMentionsResponse> {
|
||||
let form: GetUserMentions = {
|
||||
export async function getMentions(api: API): Promise<GetPersonMentionsResponse> {
|
||||
let form: GetPersonMentions = {
|
||||
sort: SortType.New,
|
||||
unread_only: false,
|
||||
auth: api.auth,
|
||||
};
|
||||
return api.client.getUserMentions(form);
|
||||
return api.client.getPersonMentions(form);
|
||||
}
|
||||
|
||||
export async function likeComment(
|
||||
|
@ -448,7 +448,6 @@ export async function createCommunity(
|
|||
description,
|
||||
icon,
|
||||
banner,
|
||||
category_id: 1,
|
||||
nsfw: false,
|
||||
auth: api.auth,
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
getSite,
|
||||
} from './shared';
|
||||
import {
|
||||
UserViewSafe,
|
||||
PersonViewSafe,
|
||||
SaveUserSettings,
|
||||
SortType,
|
||||
ListingType,
|
||||
|
@ -17,14 +17,14 @@ import {
|
|||
let auth: string;
|
||||
let apShortname: string;
|
||||
|
||||
function assertUserFederation(userOne: UserViewSafe, userTwo: UserViewSafe) {
|
||||
expect(userOne.user.name).toBe(userTwo.user.name);
|
||||
expect(userOne.user.preferred_username).toBe(userTwo.user.preferred_username);
|
||||
expect(userOne.user.bio).toBe(userTwo.user.bio);
|
||||
expect(userOne.user.actor_id).toBe(userTwo.user.actor_id);
|
||||
expect(userOne.user.avatar).toBe(userTwo.user.avatar);
|
||||
expect(userOne.user.banner).toBe(userTwo.user.banner);
|
||||
expect(userOne.user.published).toBe(userTwo.user.published);
|
||||
function assertUserFederation(userOne: PersonViewSafe, userTwo: PersonViewSafe) {
|
||||
expect(userOne.person.name).toBe(userTwo.person.name);
|
||||
expect(userOne.person.preferred_username).toBe(userTwo.person.preferred_username);
|
||||
expect(userOne.person.bio).toBe(userTwo.person.bio);
|
||||
expect(userOne.person.actor_id).toBe(userTwo.person.actor_id);
|
||||
expect(userOne.person.avatar).toBe(userTwo.person.avatar);
|
||||
expect(userOne.person.banner).toBe(userTwo.person.banner);
|
||||
expect(userOne.person.published).toBe(userTwo.person.published);
|
||||
}
|
||||
|
||||
test('Create user', async () => {
|
||||
|
@ -34,7 +34,7 @@ test('Create user', async () => {
|
|||
|
||||
let site = await getSite(alpha, auth);
|
||||
expect(site.my_user).toBeDefined();
|
||||
apShortname = `@${site.my_user.name}@lemmy-alpha:8541`;
|
||||
apShortname = `@${site.my_user.person.name}@lemmy-alpha:8541`;
|
||||
});
|
||||
|
||||
test('Set some user settings, check that they are federated', async () => {
|
||||
|
|
|
@ -293,10 +293,10 @@
|
|||
exec-sh "^0.3.2"
|
||||
minimist "^1.2.0"
|
||||
|
||||
"@eslint/eslintrc@^0.2.2":
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.2.tgz#d01fc791e2fc33e88a29d6f3dc7e93d0cd784b76"
|
||||
integrity sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==
|
||||
"@eslint/eslintrc@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318"
|
||||
integrity sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==
|
||||
dependencies:
|
||||
ajv "^6.12.4"
|
||||
debug "^4.1.1"
|
||||
|
@ -305,7 +305,7 @@
|
|||
ignore "^4.0.6"
|
||||
import-fresh "^3.2.1"
|
||||
js-yaml "^3.13.1"
|
||||
lodash "^4.17.19"
|
||||
lodash "^4.17.20"
|
||||
minimatch "^3.0.4"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
|
@ -590,7 +590,7 @@
|
|||
dependencies:
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/jest@26.x", "@types/jest@^26.0.19":
|
||||
"@types/jest@26.x":
|
||||
version "26.0.19"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.19.tgz#e6fa1e3def5842ec85045bd5210e9bb8289de790"
|
||||
integrity sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ==
|
||||
|
@ -598,6 +598,14 @@
|
|||
jest-diff "^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":
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
|
||||
|
@ -1825,13 +1833,13 @@ eslint-visitor-keys@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
|
||||
integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
|
||||
|
||||
eslint@^7.10.0:
|
||||
version "7.17.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.17.0.tgz#4ccda5bf12572ad3bf760e6f195886f50569adb0"
|
||||
integrity sha512-zJk08MiBgwuGoxes5sSQhOtibZ75pz0J35XTRlZOk9xMffhpA9BTbQZxoXZzOl5zMbleShbGwtw+1kGferfFwQ==
|
||||
eslint@^7.18.0:
|
||||
version "7.18.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.18.0.tgz#7fdcd2f3715a41fe6295a16234bd69aed2c75e67"
|
||||
integrity sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@eslint/eslintrc" "^0.2.2"
|
||||
"@eslint/eslintrc" "^0.3.0"
|
||||
ajv "^6.10.0"
|
||||
chalk "^4.0.0"
|
||||
cross-spawn "^7.0.2"
|
||||
|
@ -1855,7 +1863,7 @@ eslint@^7.10.0:
|
|||
js-yaml "^3.13.1"
|
||||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
levn "^0.4.1"
|
||||
lodash "^4.17.19"
|
||||
lodash "^4.17.20"
|
||||
minimatch "^3.0.4"
|
||||
natural-compare "^1.4.0"
|
||||
optionator "^0.9.1"
|
||||
|
@ -3225,10 +3233,10 @@ language-tags@^1.0.5:
|
|||
dependencies:
|
||||
language-subtag-registry "~0.3.2"
|
||||
|
||||
lemmy-js-client@0.9.0-rc.12:
|
||||
version "0.9.0-rc.12"
|
||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.9.0-rc.12.tgz#991d31c4ef89b9bd4088a17c60b6cbaac997df41"
|
||||
integrity sha512-SeCw9wjU89Zm4YWhr+neHC2XvqoqzJg2e42sFEgcDmnQxpPt2sND9Udu+tjGXatbz0tCu6ybGmpR5M0QT4xx9Q==
|
||||
lemmy-js-client@0.10.0-rc.4:
|
||||
version "0.10.0-rc.4"
|
||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.10.0-rc.4.tgz#ac6fe6940fc5f73260ddb166ce0ef3c0520901fc"
|
||||
integrity sha512-yJPnvGaWneOOwjKEqb4qXtQk+4DbRgO+hEzSin2GgUgnxluY43gemwiCPt6EnV+j4ueKoi0+QORVg2RuRC2PaQ==
|
||||
|
||||
leven@^3.1.0:
|
||||
version "3.1.0"
|
||||
|
|
|
@ -35,8 +35,6 @@
|
|||
tls_enabled: true
|
||||
# json web token for authorization between server and client
|
||||
jwt_secret: "changeme"
|
||||
# path to built documentation
|
||||
docs_dir: "/app/documentation"
|
||||
# address where pictrs is available
|
||||
pictrs_url: "http://pictrs:8080"
|
||||
# address where iframely is available
|
||||
|
@ -65,12 +63,13 @@
|
|||
# whether to enable activitypub federation.
|
||||
enabled: false
|
||||
# Allows and blocks are described here:
|
||||
# https://lemmy.ml/docs/administration_federation.html#instance-allowlist-and-blocklist
|
||||
# https://join.lemmy.ml/docs/en/federation/administration.html#instance-allowlist-and-blocklist
|
||||
#
|
||||
# comma separated list of instances with which federation is allowed
|
||||
allowed_instances: ""
|
||||
# Only one of these blocks should be uncommented
|
||||
# allowed_instances: ["instance1.tld","instance2.tld"]
|
||||
# comma separated list of instances which are blocked from federating
|
||||
blocked_instances: ""
|
||||
# blocked_instances: []
|
||||
}
|
||||
captcha: {
|
||||
enabled: true
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "lemmy_api"
|
||||
version = "0.1.0"
|
||||
authors = ["Felix Ableitner <me@nutomic.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "lemmy_api"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
lemmy_apub = { path = "../apub" }
|
||||
|
@ -16,36 +16,35 @@ 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_structs = { path = "../structs" }
|
||||
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.60", features = ["preserve_order"] }
|
||||
serde = { version = "1.0.118", features = ["derive"] }
|
||||
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.11"
|
||||
rand = "0.8.0"
|
||||
log = "0.4.14"
|
||||
rand = "0.8.3"
|
||||
strum = "0.20.0"
|
||||
strum_macros = "0.20.1"
|
||||
jsonwebtoken = "7.2.0"
|
||||
lazy_static = "1.4.0"
|
||||
url = { version = "2.2.0", features = ["serde"] }
|
||||
openssl = "0.10.31"
|
||||
http = "0.2.2"
|
||||
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.8"
|
||||
itertools = "0.9.0"
|
||||
uuid = { version = "0.8.1", features = ["serde", "v4"] }
|
||||
sha2 = "0.9.2"
|
||||
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.36"
|
||||
thiserror = "1.0.22"
|
||||
anyhow = "1.0.38"
|
||||
thiserror = "1.0.23"
|
||||
background-jobs = "0.8.0"
|
||||
reqwest = { version = "0.10.10", features = ["json"] }
|
||||
|
|
|
@ -2,14 +2,15 @@ use crate::{
|
|||
check_community_ban,
|
||||
check_downvotes_enabled,
|
||||
collect_moderated_communities,
|
||||
get_local_user_view_from_jwt,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
get_post,
|
||||
get_user_from_jwt,
|
||||
get_user_from_jwt_opt,
|
||||
is_mod_or_admin,
|
||||
Perform,
|
||||
};
|
||||
use actix_web::web::Data;
|
||||
use lemmy_apub::{ApubLikeableType, ApubObjectType};
|
||||
use lemmy_api_structs::{blocking, comment::*, send_local_notifs};
|
||||
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
|
||||
use lemmy_db_queries::{
|
||||
source::comment::Comment_,
|
||||
Crud,
|
||||
|
@ -19,16 +20,18 @@ use lemmy_db_queries::{
|
|||
Saveable,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_schema::source::{comment::*, comment_report::*, moderator::*};
|
||||
use lemmy_db_schema::{
|
||||
source::{comment::*, comment_report::*, moderator::*},
|
||||
LocalUserId,
|
||||
};
|
||||
use lemmy_db_views::{
|
||||
comment_report_view::{CommentReportQueryBuilder, CommentReportView},
|
||||
comment_view::{CommentQueryBuilder, CommentView},
|
||||
local_user_view::LocalUserView,
|
||||
};
|
||||
use lemmy_structs::{blocking, comment::*, send_local_notifs};
|
||||
use lemmy_utils::{
|
||||
apub::{make_apub_endpoint, EndpointType},
|
||||
utils::{remove_slurs, scrape_text_for_mentions},
|
||||
APIError,
|
||||
ApiError,
|
||||
ConnectionId,
|
||||
LemmyError,
|
||||
};
|
||||
|
@ -49,7 +52,7 @@ impl Perform for CreateComment {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &CreateComment = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let content_slurs_removed = remove_slurs(&data.content.to_owned());
|
||||
|
||||
|
@ -57,18 +60,31 @@ impl Perform for CreateComment {
|
|||
let post_id = data.post_id;
|
||||
let post = get_post(post_id, context.pool()).await?;
|
||||
|
||||
check_community_ban(user.id, post.community_id, context.pool()).await?;
|
||||
check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?;
|
||||
|
||||
// Check if post is locked, no new comments
|
||||
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 {
|
||||
content: content_slurs_removed,
|
||||
parent_id: data.parent_id.to_owned(),
|
||||
post_id: data.post_id,
|
||||
creator_id: user.id,
|
||||
creator_id: local_user_view.person.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
read: None,
|
||||
|
@ -86,23 +102,26 @@ impl Perform for CreateComment {
|
|||
.await?
|
||||
{
|
||||
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
|
||||
let inserted_comment_id = inserted_comment.id;
|
||||
let updated_comment: Comment = match blocking(context.pool(), move |conn| {
|
||||
let updated_comment: Comment =
|
||||
match blocking(context.pool(), move |conn| -> Result<Comment, LemmyError> {
|
||||
let apub_id =
|
||||
make_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string()).to_string();
|
||||
Comment::update_ap_id(&conn, inserted_comment_id, apub_id)
|
||||
generate_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string())?;
|
||||
Ok(Comment::update_ap_id(&conn, inserted_comment_id, apub_id)?)
|
||||
})
|
||||
.await?
|
||||
{
|
||||
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.send_create(&user, context).await?;
|
||||
updated_comment
|
||||
.send_create(&local_user_view.person, context)
|
||||
.await?;
|
||||
|
||||
// Scan the comment for user mentions, add those rows
|
||||
let post_id = post.id;
|
||||
|
@ -110,7 +129,7 @@ impl Perform for CreateComment {
|
|||
let recipient_ids = send_local_notifs(
|
||||
mentions,
|
||||
updated_comment.clone(),
|
||||
&user,
|
||||
local_user_view.person.clone(),
|
||||
post,
|
||||
context.pool(),
|
||||
true,
|
||||
|
@ -121,33 +140,35 @@ impl Perform for CreateComment {
|
|||
let like_form = CommentLikeForm {
|
||||
comment_id: inserted_comment.id,
|
||||
post_id,
|
||||
user_id: user.id,
|
||||
person_id: local_user_view.person.id,
|
||||
score: 1,
|
||||
};
|
||||
|
||||
let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
|
||||
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.send_like(&user, context).await?;
|
||||
updated_comment
|
||||
.send_like(&local_user_view.person, context)
|
||||
.await?;
|
||||
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let mut comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(&conn, inserted_comment.id, Some(user_id))
|
||||
CommentView::read(&conn, inserted_comment.id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// If its a comment to yourself, mark it as read
|
||||
let comment_id = comment_view.comment.id;
|
||||
if user.id == comment_view.get_recipient_id() {
|
||||
if local_user_view.person.id == comment_view.get_recipient_id() {
|
||||
match blocking(context.pool(), move |conn| {
|
||||
Comment::update_read(conn, comment_id, true)
|
||||
})
|
||||
.await?
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
@ -180,7 +201,7 @@ impl Perform for EditComment {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &EditComment = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = blocking(context.pool(), move |conn| {
|
||||
|
@ -188,11 +209,16 @@ impl Perform for EditComment {
|
|||
})
|
||||
.await??;
|
||||
|
||||
check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
orig_comment.community.id,
|
||||
context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Verify that only the creator can edit
|
||||
if user.id != orig_comment.creator.id {
|
||||
return Err(APIError::err("no_comment_edit_allowed").into());
|
||||
if local_user_view.person.id != orig_comment.creator.id {
|
||||
return Err(ApiError::err("no_comment_edit_allowed").into());
|
||||
}
|
||||
|
||||
// Do the update
|
||||
|
@ -204,11 +230,13 @@ impl Perform for EditComment {
|
|||
.await?
|
||||
{
|
||||
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
|
||||
updated_comment.send_update(&user, context).await?;
|
||||
updated_comment
|
||||
.send_update(&local_user_view.person, context)
|
||||
.await?;
|
||||
|
||||
// Do the mentions / recipients
|
||||
let updated_comment_content = updated_comment.content.to_owned();
|
||||
|
@ -216,7 +244,7 @@ impl Perform for EditComment {
|
|||
let recipient_ids = send_local_notifs(
|
||||
mentions,
|
||||
updated_comment,
|
||||
&user,
|
||||
local_user_view.person.clone(),
|
||||
orig_comment.post,
|
||||
context.pool(),
|
||||
false,
|
||||
|
@ -224,9 +252,9 @@ impl Perform for EditComment {
|
|||
.await?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, Some(user_id))
|
||||
CommentView::read(conn, comment_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -256,7 +284,7 @@ impl Perform for DeleteComment {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &DeleteComment = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = blocking(context.pool(), move |conn| {
|
||||
|
@ -264,11 +292,16 @@ impl Perform for DeleteComment {
|
|||
})
|
||||
.await??;
|
||||
|
||||
check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
orig_comment.community.id,
|
||||
context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Verify that only the creator can delete
|
||||
if user.id != orig_comment.creator.id {
|
||||
return Err(APIError::err("no_comment_edit_allowed").into());
|
||||
if local_user_view.person.id != orig_comment.creator.id {
|
||||
return Err(ApiError::err("no_comment_edit_allowed").into());
|
||||
}
|
||||
|
||||
// Do the delete
|
||||
|
@ -279,21 +312,25 @@ impl Perform for DeleteComment {
|
|||
.await?
|
||||
{
|
||||
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
|
||||
if deleted {
|
||||
updated_comment.send_delete(&user, context).await?;
|
||||
updated_comment
|
||||
.send_delete(&local_user_view.person, context)
|
||||
.await?;
|
||||
} else {
|
||||
updated_comment.send_undo_delete(&user, context).await?;
|
||||
updated_comment
|
||||
.send_undo_delete(&local_user_view.person, context)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Refetch it
|
||||
let comment_id = data.comment_id;
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, Some(user_id))
|
||||
CommentView::read(conn, comment_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -303,7 +340,7 @@ impl Perform for DeleteComment {
|
|||
let recipient_ids = send_local_notifs(
|
||||
mentions,
|
||||
updated_comment,
|
||||
&user,
|
||||
local_user_view.person.clone(),
|
||||
comment_view_2.post,
|
||||
context.pool(),
|
||||
false,
|
||||
|
@ -336,7 +373,7 @@ impl Perform for RemoveComment {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &RemoveComment = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = blocking(context.pool(), move |conn| {
|
||||
|
@ -344,10 +381,20 @@ impl Perform for RemoveComment {
|
|||
})
|
||||
.await??;
|
||||
|
||||
check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
orig_comment.community.id,
|
||||
context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Verify that only a mod or admin can remove
|
||||
is_mod_or_admin(context.pool(), user.id, orig_comment.community.id).await?;
|
||||
is_mod_or_admin(
|
||||
context.pool(),
|
||||
local_user_view.person.id,
|
||||
orig_comment.community.id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Do the remove
|
||||
let removed = data.removed;
|
||||
|
@ -357,12 +404,12 @@ impl Perform for RemoveComment {
|
|||
.await?
|
||||
{
|
||||
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
|
||||
let form = ModRemoveCommentForm {
|
||||
mod_user_id: user.id,
|
||||
mod_person_id: local_user_view.person.id,
|
||||
comment_id: data.comment_id,
|
||||
removed: Some(removed),
|
||||
reason: data.reason.to_owned(),
|
||||
|
@ -374,16 +421,20 @@ impl Perform for RemoveComment {
|
|||
|
||||
// Send the apub message
|
||||
if removed {
|
||||
updated_comment.send_remove(&user, context).await?;
|
||||
updated_comment
|
||||
.send_remove(&local_user_view.person, context)
|
||||
.await?;
|
||||
} else {
|
||||
updated_comment.send_undo_remove(&user, context).await?;
|
||||
updated_comment
|
||||
.send_undo_remove(&local_user_view.person, context)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Refetch it
|
||||
let comment_id = data.comment_id;
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, Some(user_id))
|
||||
CommentView::read(conn, comment_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -394,7 +445,7 @@ impl Perform for RemoveComment {
|
|||
let recipient_ids = send_local_notifs(
|
||||
mentions,
|
||||
updated_comment,
|
||||
&user,
|
||||
local_user_view.person.clone(),
|
||||
comment_view_2.post,
|
||||
context.pool(),
|
||||
false,
|
||||
|
@ -427,7 +478,7 @@ impl Perform for MarkCommentAsRead {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &MarkCommentAsRead = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = blocking(context.pool(), move |conn| {
|
||||
|
@ -435,11 +486,16 @@ impl Perform for MarkCommentAsRead {
|
|||
})
|
||||
.await??;
|
||||
|
||||
check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
orig_comment.community.id,
|
||||
context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Verify that only the recipient can mark as read
|
||||
if user.id != orig_comment.get_recipient_id() {
|
||||
return Err(APIError::err("no_comment_edit_allowed").into());
|
||||
if local_user_view.person.id != orig_comment.get_recipient_id() {
|
||||
return Err(ApiError::err("no_comment_edit_allowed").into());
|
||||
}
|
||||
|
||||
// Do the mark as read
|
||||
|
@ -450,14 +506,14 @@ impl Perform for MarkCommentAsRead {
|
|||
.await?
|
||||
{
|
||||
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
|
||||
let comment_id = data.comment_id;
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, Some(user_id))
|
||||
CommentView::read(conn, comment_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -481,29 +537,29 @@ impl Perform for SaveComment {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &SaveComment = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let comment_saved_form = CommentSavedForm {
|
||||
comment_id: data.comment_id,
|
||||
user_id: user.id,
|
||||
person_id: local_user_view.person.id,
|
||||
};
|
||||
|
||||
if data.save {
|
||||
let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
|
||||
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 {
|
||||
let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
|
||||
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 user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, Some(user_id))
|
||||
CommentView::read(conn, comment_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -525,9 +581,9 @@ impl Perform for CreateCommentLike {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &CreateCommentLike = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let mut recipient_ids = Vec::new();
|
||||
let mut recipient_ids = Vec::<LocalUserId>::new();
|
||||
|
||||
// Don't do a downvote if site has downvotes disabled
|
||||
check_downvotes_enabled(data.score, context.pool()).await?;
|
||||
|
@ -538,22 +594,34 @@ impl Perform for CreateCommentLike {
|
|||
})
|
||||
.await??;
|
||||
|
||||
check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
orig_comment.community.id,
|
||||
context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Add parent user to recipients
|
||||
recipient_ids.push(orig_comment.get_recipient_id());
|
||||
let recipient_id = 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 {
|
||||
comment_id: data.comment_id,
|
||||
post_id: orig_comment.post.id,
|
||||
user_id: user.id,
|
||||
person_id: local_user_view.person.id,
|
||||
score: data.score,
|
||||
};
|
||||
|
||||
// Remove any likes first
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommentLike::remove(conn, user_id, comment_id)
|
||||
CommentLike::remove(conn, person_id, comment_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -564,23 +632,27 @@ impl Perform for CreateCommentLike {
|
|||
let like_form2 = like_form.clone();
|
||||
let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
|
||||
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 {
|
||||
comment.send_like(&user, context).await?;
|
||||
comment.send_like(&local_user_view.person, context).await?;
|
||||
} else if like_form.score == -1 {
|
||||
comment.send_dislike(&user, context).await?;
|
||||
comment
|
||||
.send_dislike(&local_user_view.person, context)
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
comment.send_undo_like(&user, context).await?;
|
||||
comment
|
||||
.send_undo_like(&local_user_view.person, context)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Have to refetch the comment to get the current state
|
||||
let comment_id = data.comment_id;
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let liked_comment = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, Some(user_id))
|
||||
CommentView::read(conn, comment_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -610,8 +682,8 @@ impl Perform for GetComments {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetCommentsResponse, LemmyError> {
|
||||
let data: &GetComments = &self;
|
||||
let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let user_id = user.map(|u| u.id);
|
||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let person_id = local_user_view.map(|u| u.person.id);
|
||||
|
||||
let type_ = ListingType::from_str(&data.type_)?;
|
||||
let sort = SortType::from_str(&data.sort)?;
|
||||
|
@ -626,7 +698,7 @@ impl Perform for GetComments {
|
|||
.sort(&sort)
|
||||
.community_id(community_id)
|
||||
.community_name(community_name)
|
||||
.my_user_id(user_id)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
|
@ -634,7 +706,7 @@ impl Perform for GetComments {
|
|||
.await?;
|
||||
let comments = match 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 })
|
||||
|
@ -652,28 +724,28 @@ impl Perform for CreateCommentReport {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CreateCommentReportResponse, LemmyError> {
|
||||
let data: &CreateCommentReport = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
// check size of report and check for whitespace
|
||||
let reason = data.reason.trim();
|
||||
if reason.is_empty() {
|
||||
return Err(APIError::err("report_reason_required").into());
|
||||
return Err(ApiError::err("report_reason_required").into());
|
||||
}
|
||||
if reason.len() > 1000 {
|
||||
return Err(APIError::err("report_too_long").into());
|
||||
if reason.chars().count() > 1000 {
|
||||
return Err(ApiError::err("report_too_long").into());
|
||||
}
|
||||
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_id = data.comment_id;
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(&conn, comment_id, None)
|
||||
})
|
||||
.await??;
|
||||
|
||||
check_community_ban(user_id, comment_view.community.id, context.pool()).await?;
|
||||
check_community_ban(person_id, comment_view.community.id, context.pool()).await?;
|
||||
|
||||
let report_form = CommentReportForm {
|
||||
creator_id: user_id,
|
||||
creator_id: person_id,
|
||||
comment_id,
|
||||
original_comment_text: comment_view.comment.content,
|
||||
reason: data.reason.to_owned(),
|
||||
|
@ -685,7 +757,7 @@ impl Perform for CreateCommentReport {
|
|||
.await?
|
||||
{
|
||||
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 };
|
||||
|
@ -693,7 +765,7 @@ impl Perform for CreateCommentReport {
|
|||
context.chat_server().do_send(SendUserRoomMessage {
|
||||
op: UserOperation::CreateCommentReport,
|
||||
response: res.clone(),
|
||||
recipient_id: user.id,
|
||||
local_recipient_id: local_user_view.local_user.id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
|
@ -719,7 +791,7 @@ impl Perform for ResolveCommentReport {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<ResolveCommentReportResponse, LemmyError> {
|
||||
let data: &ResolveCommentReport = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let report_id = data.report_id;
|
||||
let report = blocking(context.pool(), move |conn| {
|
||||
|
@ -727,20 +799,20 @@ impl Perform for ResolveCommentReport {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let user_id = user.id;
|
||||
is_mod_or_admin(context.pool(), user_id, report.community.id).await?;
|
||||
let person_id = local_user_view.person.id;
|
||||
is_mod_or_admin(context.pool(), person_id, report.community.id).await?;
|
||||
|
||||
let resolved = data.resolved;
|
||||
let resolve_fun = move |conn: &'_ _| {
|
||||
if resolved {
|
||||
CommentReport::resolve(conn, report_id, user_id)
|
||||
CommentReport::resolve(conn, report_id, person_id)
|
||||
} else {
|
||||
CommentReport::unresolve(conn, report_id, user_id)
|
||||
CommentReport::unresolve(conn, report_id, person_id)
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
@ -772,12 +844,12 @@ impl Perform for ListCommentReports {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<ListCommentReportsResponse, LemmyError> {
|
||||
let data: &ListCommentReports = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_id = data.community;
|
||||
let community_ids =
|
||||
collect_moderated_communities(user_id, community_id, context.pool()).await?;
|
||||
collect_moderated_communities(person_id, community_id, context.pool()).await?;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
|
@ -795,7 +867,7 @@ impl Perform for ListCommentReports {
|
|||
context.chat_server().do_send(SendUserRoomMessage {
|
||||
op: UserOperation::ListCommentReports,
|
||||
response: res.clone(),
|
||||
recipient_id: user.id,
|
||||
local_recipient_id: local_user_view.local_user.id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
use crate::{
|
||||
check_optional_url,
|
||||
get_user_from_jwt,
|
||||
get_user_from_jwt_opt,
|
||||
check_community_ban,
|
||||
get_local_user_view_from_jwt,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
is_admin,
|
||||
is_mod_or_admin,
|
||||
Perform,
|
||||
};
|
||||
use actix_web::web::Data;
|
||||
use anyhow::Context;
|
||||
use lemmy_apub::ActorType;
|
||||
use lemmy_api_structs::{blocking, community::*};
|
||||
use lemmy_apub::{
|
||||
generate_apub_endpoint,
|
||||
generate_followers_url,
|
||||
generate_inbox_url,
|
||||
generate_shared_inbox_url,
|
||||
ActorType,
|
||||
EndpointType,
|
||||
};
|
||||
use lemmy_db_queries::{
|
||||
diesel_option_overwrite,
|
||||
diesel_option_overwrite_to_url,
|
||||
source::{
|
||||
comment::Comment_,
|
||||
community::{CommunityModerator_, Community_},
|
||||
|
@ -21,25 +29,26 @@ use lemmy_db_queries::{
|
|||
Crud,
|
||||
Followable,
|
||||
Joinable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
naive_now,
|
||||
source::{comment::Comment, community::*, moderator::*, post::Post, site::*},
|
||||
PersonId,
|
||||
};
|
||||
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
||||
use lemmy_db_views_actor::{
|
||||
community_follower_view::CommunityFollowerView,
|
||||
community_moderator_view::CommunityModeratorView,
|
||||
community_view::{CommunityQueryBuilder, CommunityView},
|
||||
user_view::UserViewSafe,
|
||||
person_view::PersonViewSafe,
|
||||
};
|
||||
use lemmy_structs::{blocking, community::*};
|
||||
use lemmy_utils::{
|
||||
apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
|
||||
apub::generate_actor_keypair,
|
||||
location_info,
|
||||
utils::{check_slurs, check_slurs_opt, is_valid_community_name, naive_from_unix},
|
||||
APIError,
|
||||
ApiError,
|
||||
ConnectionId,
|
||||
LemmyError,
|
||||
};
|
||||
|
@ -60,8 +69,8 @@ impl Perform for GetCommunity {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetCommunityResponse, LemmyError> {
|
||||
let data: &GetCommunity = &self;
|
||||
let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let user_id = user.map(|u| u.id);
|
||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let person_id = local_user_view.map(|u| u.person.id);
|
||||
|
||||
let community_id = match data.id {
|
||||
Some(id) => id,
|
||||
|
@ -73,19 +82,19 @@ impl Perform for GetCommunity {
|
|||
.await?
|
||||
{
|
||||
Ok(community) => community,
|
||||
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
|
||||
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()),
|
||||
}
|
||||
.id
|
||||
}
|
||||
};
|
||||
|
||||
let community_view = match blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, user_id)
|
||||
CommunityView::read(conn, community_id, person_id)
|
||||
})
|
||||
.await?
|
||||
{
|
||||
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| {
|
||||
|
@ -94,7 +103,7 @@ impl Perform for GetCommunity {
|
|||
.await?
|
||||
{
|
||||
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
|
||||
|
@ -124,33 +133,30 @@ impl Perform for CreateCommunity {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommunityResponse, LemmyError> {
|
||||
let data: &CreateCommunity = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
check_slurs(&data.name)?;
|
||||
check_slurs(&data.title)?;
|
||||
check_slurs_opt(&data.description)?;
|
||||
|
||||
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
|
||||
let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string();
|
||||
let actor_id_cloned = actor_id.to_owned();
|
||||
let community_actor_id = generate_apub_endpoint(EndpointType::Community, &data.name)?;
|
||||
let actor_id_cloned = community_actor_id.to_owned();
|
||||
let community_dupe = blocking(context.pool(), move |conn| {
|
||||
Community::read_from_apub_id(conn, &actor_id_cloned)
|
||||
})
|
||||
.await?;
|
||||
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
|
||||
let icon = diesel_option_overwrite(&data.icon);
|
||||
let banner = diesel_option_overwrite(&data.banner);
|
||||
|
||||
check_optional_url(&icon)?;
|
||||
check_optional_url(&banner)?;
|
||||
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||
|
||||
// When you create a community, make sure the user becomes a moderator and a follower
|
||||
let keypair = generate_actor_keypair()?;
|
||||
|
@ -161,18 +167,20 @@ impl Perform for CreateCommunity {
|
|||
description: data.description.to_owned(),
|
||||
icon,
|
||||
banner,
|
||||
category_id: data.category_id,
|
||||
creator_id: user.id,
|
||||
creator_id: local_user_view.person.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
nsfw: data.nsfw,
|
||||
updated: None,
|
||||
actor_id: Some(actor_id),
|
||||
actor_id: Some(community_actor_id.to_owned()),
|
||||
local: true,
|
||||
private_key: Some(keypair.private_key),
|
||||
public_key: Some(keypair.public_key),
|
||||
last_refreshed_at: 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| {
|
||||
|
@ -181,35 +189,35 @@ impl Perform for CreateCommunity {
|
|||
.await?
|
||||
{
|
||||
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
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
community_id: inserted_community.id,
|
||||
user_id: user.id,
|
||||
person_id: local_user_view.person.id,
|
||||
};
|
||||
|
||||
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
||||
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
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: inserted_community.id,
|
||||
user_id: user.id,
|
||||
person_id: local_user_view.person.id,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
|
||||
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 user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, inserted_community.id, Some(user_id))
|
||||
CommunityView::read(conn, inserted_community.id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -227,20 +235,20 @@ impl Perform for EditCommunity {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommunityResponse, LemmyError> {
|
||||
let data: &EditCommunity = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
check_slurs(&data.title)?;
|
||||
check_slurs_opt(&data.description)?;
|
||||
|
||||
// Verify its a mod (only mods can edit it)
|
||||
let community_id = data.community_id;
|
||||
let mods: Vec<i32> = blocking(context.pool(), move |conn| {
|
||||
let mods: Vec<PersonId> = blocking(context.pool(), move |conn| {
|
||||
CommunityModeratorView::for_community(conn, community_id)
|
||||
.map(|v| v.into_iter().map(|m| m.moderator.id).collect())
|
||||
})
|
||||
.await??;
|
||||
if !mods.contains(&user.id) {
|
||||
return Err(APIError::err("not_a_moderator").into());
|
||||
if !mods.contains(&local_user_view.person.id) {
|
||||
return Err(ApiError::err("not_a_moderator").into());
|
||||
}
|
||||
|
||||
let community_id = data.community_id;
|
||||
|
@ -249,11 +257,8 @@ impl Perform for EditCommunity {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let icon = diesel_option_overwrite(&data.icon);
|
||||
let banner = diesel_option_overwrite(&data.banner);
|
||||
|
||||
check_optional_url(&icon)?;
|
||||
check_optional_url(&banner)?;
|
||||
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||
|
||||
let community_form = CommunityForm {
|
||||
name: read_community.name,
|
||||
|
@ -261,7 +266,6 @@ impl Perform for EditCommunity {
|
|||
description: data.description.to_owned(),
|
||||
icon,
|
||||
banner,
|
||||
category_id: data.category_id.to_owned(),
|
||||
creator_id: read_community.creator_id,
|
||||
removed: Some(read_community.removed),
|
||||
deleted: Some(read_community.deleted),
|
||||
|
@ -273,6 +277,9 @@ impl Perform for EditCommunity {
|
|||
public_key: read_community.public_key,
|
||||
last_refreshed_at: None,
|
||||
published: None,
|
||||
followers_url: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let community_id = data.community_id;
|
||||
|
@ -282,16 +289,16 @@ impl Perform for EditCommunity {
|
|||
.await?
|
||||
{
|
||||
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
|
||||
// process for communities and users
|
||||
|
||||
let community_id = data.community_id;
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(user_id))
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -313,7 +320,7 @@ impl Perform for DeleteCommunity {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommunityResponse, LemmyError> {
|
||||
let data: &DeleteCommunity = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
// Verify its the creator (only a creator can delete the community)
|
||||
let community_id = data.community_id;
|
||||
|
@ -321,8 +328,8 @@ impl Perform for DeleteCommunity {
|
|||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
if read_community.creator_id != user.id {
|
||||
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
|
||||
|
@ -334,7 +341,7 @@ impl Perform for DeleteCommunity {
|
|||
.await?
|
||||
{
|
||||
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
|
||||
|
@ -345,9 +352,9 @@ impl Perform for DeleteCommunity {
|
|||
}
|
||||
|
||||
let community_id = data.community_id;
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(user_id))
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -369,10 +376,10 @@ impl Perform for RemoveCommunity {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommunityResponse, LemmyError> {
|
||||
let data: &RemoveCommunity = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
// Verify its an admin (only an admin can remove a community)
|
||||
is_admin(context.pool(), user.id).await?;
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Do the remove
|
||||
let community_id = data.community_id;
|
||||
|
@ -383,7 +390,7 @@ impl Perform for RemoveCommunity {
|
|||
.await?
|
||||
{
|
||||
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
|
||||
|
@ -392,7 +399,7 @@ impl Perform for RemoveCommunity {
|
|||
None => None,
|
||||
};
|
||||
let form = ModRemoveCommunityForm {
|
||||
mod_user_id: user.id,
|
||||
mod_person_id: local_user_view.person.id,
|
||||
community_id: data.community_id,
|
||||
removed: Some(removed),
|
||||
reason: data.reason.to_owned(),
|
||||
|
@ -411,9 +418,9 @@ impl Perform for RemoveCommunity {
|
|||
}
|
||||
|
||||
let community_id = data.community_id;
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(user_id))
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -435,27 +442,30 @@ impl Perform for ListCommunities {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<ListCommunitiesResponse, LemmyError> {
|
||||
let data: &ListCommunities = &self;
|
||||
let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
|
||||
let user_id = match &user {
|
||||
Some(user) => Some(user.id),
|
||||
let person_id = match &local_user_view {
|
||||
Some(uv) => Some(uv.person.id),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let show_nsfw = match &user {
|
||||
Some(user) => user.show_nsfw,
|
||||
// Don't show NSFW by default
|
||||
let show_nsfw = match &local_user_view {
|
||||
Some(uv) => uv.local_user.show_nsfw,
|
||||
None => false,
|
||||
};
|
||||
|
||||
let type_ = ListingType::from_str(&data.type_)?;
|
||||
let sort = SortType::from_str(&data.sort)?;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let communities = blocking(context.pool(), move |conn| {
|
||||
CommunityQueryBuilder::create(conn)
|
||||
.listing_type(&type_)
|
||||
.sort(&sort)
|
||||
.show_nsfw(show_nsfw)
|
||||
.my_user_id(user_id)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
|
@ -477,7 +487,7 @@ impl Perform for FollowCommunity {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommunityResponse, LemmyError> {
|
||||
let data: &FollowCommunity = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
|
@ -486,39 +496,47 @@ impl Perform for FollowCommunity {
|
|||
.await??;
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: data.community_id,
|
||||
user_id: user.id,
|
||||
person_id: local_user_view.person.id,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
if community.local {
|
||||
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);
|
||||
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 {
|
||||
let unfollow =
|
||||
move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
|
||||
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 {
|
||||
// Dont actually add to the community followers here, because you need
|
||||
// to wait for the accept
|
||||
user.send_follow(&community.actor_id()?, context).await?;
|
||||
local_user_view
|
||||
.person
|
||||
.send_follow(&community.actor_id(), context)
|
||||
.await?;
|
||||
} else {
|
||||
user.send_unfollow(&community.actor_id()?, context).await?;
|
||||
local_user_view
|
||||
.person
|
||||
.send_unfollow(&community.actor_id(), context)
|
||||
.await?;
|
||||
let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
|
||||
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 user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let mut community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(user_id))
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -543,16 +561,16 @@ impl Perform for GetFollowedCommunities {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
|
||||
let data: &GetFollowedCommunities = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let communities = match blocking(context.pool(), move |conn| {
|
||||
CommunityFollowerView::for_user(conn, user_id)
|
||||
CommunityFollowerView::for_person(conn, person_id)
|
||||
})
|
||||
.await?
|
||||
{
|
||||
Ok(communities) => communities,
|
||||
_ => return Err(APIError::err("system_err_login").into()),
|
||||
_ => return Err(ApiError::err("system_err_login").into()),
|
||||
};
|
||||
|
||||
// Return the jwt
|
||||
|
@ -570,28 +588,40 @@ impl Perform for BanFromCommunity {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<BanFromCommunityResponse, LemmyError> {
|
||||
let data: &BanFromCommunity = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let banned_user_id = data.user_id;
|
||||
let banned_person_id = data.person_id;
|
||||
|
||||
// Verify that only mods or admins can ban
|
||||
is_mod_or_admin(context.pool(), user.id, community_id).await?;
|
||||
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
|
||||
|
||||
let community_user_ban_form = CommunityUserBanForm {
|
||||
let community_user_ban_form = CommunityPersonBanForm {
|
||||
community_id: data.community_id,
|
||||
user_id: data.user_id,
|
||||
person_id: data.person_id,
|
||||
};
|
||||
|
||||
if data.ban {
|
||||
let ban = move |conn: &'_ _| CommunityUserBan::ban(conn, &community_user_ban_form);
|
||||
let ban = move |conn: &'_ _| CommunityPersonBan::ban(conn, &community_user_ban_form);
|
||||
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 {
|
||||
let unban = move |conn: &'_ _| CommunityUserBan::unban(conn, &community_user_ban_form);
|
||||
let unban = move |conn: &'_ _| CommunityPersonBan::unban(conn, &community_user_ban_form);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -599,7 +629,7 @@ impl Perform for BanFromCommunity {
|
|||
if data.remove_data {
|
||||
// Posts
|
||||
blocking(context.pool(), move |conn: &'_ _| {
|
||||
Post::update_removed_for_creator(conn, banned_user_id, Some(community_id), true)
|
||||
Post::update_removed_for_creator(conn, banned_person_id, Some(community_id), true)
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -607,7 +637,7 @@ impl Perform for BanFromCommunity {
|
|||
// TODO Diesel doesn't allow updates with joins, so this has to be a loop
|
||||
let comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.creator_id(banned_user_id)
|
||||
.creator_id(banned_person_id)
|
||||
.community_id(community_id)
|
||||
.limit(std::i64::MAX)
|
||||
.list()
|
||||
|
@ -631,8 +661,8 @@ impl Perform for BanFromCommunity {
|
|||
};
|
||||
|
||||
let form = ModBanFromCommunityForm {
|
||||
mod_user_id: user.id,
|
||||
other_user_id: data.user_id,
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
community_id: data.community_id,
|
||||
reason: data.reason.to_owned(),
|
||||
banned: Some(data.ban),
|
||||
|
@ -643,14 +673,14 @@ impl Perform for BanFromCommunity {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let user_id = data.user_id;
|
||||
let user_view = blocking(context.pool(), move |conn| {
|
||||
UserViewSafe::read(conn, user_id)
|
||||
let person_id = data.person_id;
|
||||
let person_view = blocking(context.pool(), move |conn| {
|
||||
PersonViewSafe::read(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = BanFromCommunityResponse {
|
||||
user_view,
|
||||
person_view,
|
||||
banned: data.ban,
|
||||
};
|
||||
|
||||
|
@ -675,34 +705,34 @@ impl Perform for AddModToCommunity {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<AddModToCommunityResponse, LemmyError> {
|
||||
let data: &AddModToCommunity = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
community_id: data.community_id,
|
||||
user_id: data.user_id,
|
||||
person_id: data.person_id,
|
||||
};
|
||||
|
||||
let community_id = data.community_id;
|
||||
|
||||
// Verify that only mods or admins can add mod
|
||||
is_mod_or_admin(context.pool(), user.id, community_id).await?;
|
||||
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
|
||||
|
||||
if data.added {
|
||||
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
||||
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 {
|
||||
let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
|
||||
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
|
||||
let form = ModAddCommunityForm {
|
||||
mod_user_id: user.id,
|
||||
other_user_id: data.user_id,
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
community_id: data.community_id,
|
||||
removed: Some(!data.added),
|
||||
};
|
||||
|
@ -740,7 +770,7 @@ impl Perform for TransferCommunity {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetCommunityResponse, LemmyError> {
|
||||
let data: &TransferCommunity = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let read_community = blocking(context.pool(), move |conn| {
|
||||
|
@ -753,28 +783,31 @@ impl Perform for TransferCommunity {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
|
||||
let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
|
||||
|
||||
// Making sure the creator, if an admin, is at the top
|
||||
let creator_index = admins
|
||||
.iter()
|
||||
.position(|r| r.user.id == site_creator_id)
|
||||
.position(|r| r.person.id == site_creator_id)
|
||||
.context(location_info!())?;
|
||||
let creator_user = admins.remove(creator_index);
|
||||
admins.insert(0, creator_user);
|
||||
let creator_person = admins.remove(creator_index);
|
||||
admins.insert(0, creator_person);
|
||||
|
||||
// Make sure user is the creator, or an admin
|
||||
if user.id != read_community.creator_id
|
||||
&& !admins.iter().map(|a| a.user.id).any(|x| x == user.id)
|
||||
if local_user_view.person.id != read_community.creator_id
|
||||
&& !admins
|
||||
.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 new_creator = data.user_id;
|
||||
let new_creator = data.person_id;
|
||||
let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
|
||||
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.
|
||||
|
@ -785,10 +818,10 @@ impl Perform for TransferCommunity {
|
|||
.await??;
|
||||
let creator_index = community_mods
|
||||
.iter()
|
||||
.position(|r| r.moderator.id == data.user_id)
|
||||
.position(|r| r.moderator.id == data.person_id)
|
||||
.context(location_info!())?;
|
||||
let creator_user = community_mods.remove(creator_index);
|
||||
community_mods.insert(0, creator_user);
|
||||
let creator_person = community_mods.remove(creator_index);
|
||||
community_mods.insert(0, creator_person);
|
||||
|
||||
let community_id = data.community_id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
|
@ -800,19 +833,19 @@ impl Perform for TransferCommunity {
|
|||
for cmod in &community_mods {
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
community_id: cmod.community.id,
|
||||
user_id: cmod.moderator.id,
|
||||
person_id: cmod.moderator.id,
|
||||
};
|
||||
|
||||
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
||||
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
|
||||
let form = ModAddCommunityForm {
|
||||
mod_user_id: user.id,
|
||||
other_user_id: data.user_id,
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
community_id: data.community_id,
|
||||
removed: Some(false),
|
||||
};
|
||||
|
@ -822,14 +855,14 @@ impl Perform for TransferCommunity {
|
|||
.await??;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_view = match blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(user_id))
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await?
|
||||
{
|
||||
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;
|
||||
|
@ -839,7 +872,7 @@ impl Perform for TransferCommunity {
|
|||
.await?
|
||||
{
|
||||
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
|
||||
|
@ -857,7 +890,7 @@ fn send_community_websocket(
|
|||
websocket_id: Option<ConnectionId>,
|
||||
op: UserOperation,
|
||||
) {
|
||||
// Strip out the user id and subscribed when sending to others
|
||||
// Strip out the person id and subscribed when sending to others
|
||||
let mut res_sent = res.clone();
|
||||
res_sent.community_view.subscribed = false;
|
||||
|
||||
|
|
|
@ -1,38 +1,55 @@
|
|||
use crate::claims::Claims;
|
||||
use actix_web::{web, web::Data};
|
||||
use lemmy_api_structs::{
|
||||
blocking,
|
||||
comment::*,
|
||||
community::*,
|
||||
person::*,
|
||||
post::*,
|
||||
site::*,
|
||||
websocket::*,
|
||||
};
|
||||
use lemmy_db_queries::{
|
||||
source::{
|
||||
community::{CommunityModerator_, Community_},
|
||||
site::Site_,
|
||||
user::UserSafeSettings_,
|
||||
},
|
||||
Crud,
|
||||
DbPool,
|
||||
};
|
||||
use lemmy_db_schema::source::{
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::{Community, CommunityModerator},
|
||||
post::Post,
|
||||
site::Site,
|
||||
user::{UserSafeSettings, User_},
|
||||
},
|
||||
CommunityId,
|
||||
LocalUserId,
|
||||
PersonId,
|
||||
PostId,
|
||||
};
|
||||
use lemmy_db_views::local_user_view::{LocalUserSettingsView, LocalUserView};
|
||||
use lemmy_db_views_actor::{
|
||||
community_user_ban_view::CommunityUserBanView,
|
||||
community_person_ban_view::CommunityPersonBanView,
|
||||
community_view::CommunityView,
|
||||
};
|
||||
use lemmy_structs::{blocking, comment::*, community::*, post::*, site::*, user::*, websocket::*};
|
||||
use lemmy_utils::{settings::Settings, APIError, ConnectionId, LemmyError};
|
||||
use lemmy_utils::{
|
||||
claims::Claims,
|
||||
settings::structs::Settings,
|
||||
ApiError,
|
||||
ConnectionId,
|
||||
LemmyError,
|
||||
};
|
||||
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
|
||||
use serde::Deserialize;
|
||||
use std::process::Command;
|
||||
use std::{env, process::Command};
|
||||
use url::Url;
|
||||
|
||||
pub mod claims;
|
||||
pub mod comment;
|
||||
pub mod community;
|
||||
pub mod local_user;
|
||||
pub mod post;
|
||||
pub mod routes;
|
||||
pub mod site;
|
||||
pub mod user;
|
||||
pub mod version;
|
||||
pub mod websocket;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
|
@ -48,92 +65,121 @@ pub trait Perform {
|
|||
|
||||
pub(crate) async fn is_mod_or_admin(
|
||||
pool: &DbPool,
|
||||
user_id: i32,
|
||||
community_id: i32,
|
||||
person_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
) -> Result<(), LemmyError> {
|
||||
let is_mod_or_admin = blocking(pool, move |conn| {
|
||||
CommunityView::is_mod_or_admin(conn, user_id, community_id)
|
||||
CommunityView::is_mod_or_admin(conn, person_id, community_id)
|
||||
})
|
||||
.await?;
|
||||
if !is_mod_or_admin {
|
||||
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());
|
||||
return Err(ApiError::err("not_a_mod_or_admin").into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
|
||||
pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), 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? {
|
||||
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_user_from_jwt(jwt: &str, pool: &DbPool) -> Result<User_, LemmyError> {
|
||||
pub(crate) async fn get_local_user_view_from_jwt(
|
||||
jwt: &str,
|
||||
pool: &DbPool,
|
||||
) -> Result<LocalUserView, LemmyError> {
|
||||
let claims = match Claims::decode(&jwt) {
|
||||
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 user_id = claims.id;
|
||||
let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
|
||||
let local_user_id = LocalUserId(claims.sub);
|
||||
let local_user_view =
|
||||
blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
|
||||
// Check for a site ban
|
||||
if user.banned {
|
||||
return Err(APIError::err("site_ban").into());
|
||||
}
|
||||
Ok(user)
|
||||
if local_user_view.person.banned {
|
||||
return Err(ApiError::err("site_ban").into());
|
||||
}
|
||||
|
||||
pub(crate) async fn get_user_from_jwt_opt(
|
||||
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) 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>,
|
||||
pool: &DbPool,
|
||||
) -> Result<Option<User_>, LemmyError> {
|
||||
) -> Result<Option<LocalUserView>, LemmyError> {
|
||||
match jwt {
|
||||
Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
|
||||
Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool).await?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_user_safe_settings_from_jwt(
|
||||
pub(crate) async fn get_local_user_settings_view_from_jwt(
|
||||
jwt: &str,
|
||||
pool: &DbPool,
|
||||
) -> Result<UserSafeSettings, LemmyError> {
|
||||
) -> Result<LocalUserSettingsView, LemmyError> {
|
||||
let claims = match Claims::decode(&jwt) {
|
||||
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 user_id = claims.id;
|
||||
let user = blocking(pool, move |conn| UserSafeSettings::read(conn, user_id)).await??;
|
||||
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 user.banned {
|
||||
return Err(APIError::err("site_ban").into());
|
||||
}
|
||||
Ok(user)
|
||||
if local_user_view.person.banned {
|
||||
return Err(ApiError::err("site_ban").into());
|
||||
}
|
||||
|
||||
pub(crate) async fn get_user_safe_settings_from_jwt_opt(
|
||||
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<UserSafeSettings>, LemmyError> {
|
||||
) -> Result<Option<LocalUserSettingsView>, LemmyError> {
|
||||
match jwt {
|
||||
Some(jwt) => Ok(Some(get_user_safe_settings_from_jwt(jwt, pool).await?)),
|
||||
Some(jwt) => Ok(Some(
|
||||
get_local_user_settings_view_from_jwt(jwt, pool).await?,
|
||||
)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn check_community_ban(
|
||||
user_id: i32,
|
||||
community_id: i32,
|
||||
person_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
pool: &DbPool,
|
||||
) -> Result<(), LemmyError> {
|
||||
let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
|
||||
let is_banned =
|
||||
move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
|
||||
if blocking(pool, is_banned).await? {
|
||||
Err(APIError::err("community_ban").into())
|
||||
Err(ApiError::err("community_ban").into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -143,7 +189,7 @@ pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result
|
|||
if score == -1 {
|
||||
let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
|
||||
if !site.enable_downvotes {
|
||||
return Err(APIError::err("downvotes_disabled").into());
|
||||
return Err(ApiError::err("downvotes_disabled").into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -153,63 +199,64 @@ 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
|
||||
/// of that community and returns the community id in a vec
|
||||
///
|
||||
/// * `user_id` - the user id of the moderator
|
||||
/// * `person_id` - the person id of the moderator
|
||||
/// * `community_id` - optional community id to check for moderator privileges
|
||||
/// * `pool` - the diesel db pool
|
||||
pub(crate) async fn collect_moderated_communities(
|
||||
user_id: i32,
|
||||
community_id: Option<i32>,
|
||||
person_id: PersonId,
|
||||
community_id: Option<CommunityId>,
|
||||
pool: &DbPool,
|
||||
) -> Result<Vec<i32>, LemmyError> {
|
||||
) -> Result<Vec<CommunityId>, LemmyError> {
|
||||
if let Some(community_id) = community_id {
|
||||
// if the user provides a community_id, just check for mod/admin privileges
|
||||
is_mod_or_admin(pool, user_id, community_id).await?;
|
||||
is_mod_or_admin(pool, person_id, community_id).await?;
|
||||
Ok(vec![community_id])
|
||||
} else {
|
||||
let ids = blocking(pool, move |conn: &'_ _| {
|
||||
CommunityModerator::get_user_moderated_communities(conn, user_id)
|
||||
CommunityModerator::get_person_moderated_communities(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
Ok(ids)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
|
||||
if let Some(Some(item)) = &item {
|
||||
if Url::parse(item).is_err() {
|
||||
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 {
|
||||
pub(crate) async fn build_federated_instances(
|
||||
pool: &DbPool,
|
||||
) -> Result<Option<FederatedInstances>, LemmyError> {
|
||||
if Settings::get().federation().enabled {
|
||||
let distinct_communities = blocking(pool, move |conn| {
|
||||
Community::distinct_federated_communities(conn)
|
||||
})
|
||||
.await??;
|
||||
|
||||
instances = distinct_communities
|
||||
let allowed = Settings::get().get_allowed_instances();
|
||||
let blocked = Settings::get().get_blocked_instances();
|
||||
|
||||
let mut linked = distinct_communities
|
||||
.iter()
|
||||
.map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
|
||||
.collect::<Result<Vec<String>, LemmyError>>()?;
|
||||
|
||||
instances.append(&mut Settings::get().get_allowed_instances());
|
||||
instances.retain(|a| {
|
||||
!Settings::get().get_blocked_instances().contains(a)
|
||||
&& !a.eq("")
|
||||
&& !a.eq(&Settings::get().hostname)
|
||||
});
|
||||
|
||||
// Sort and remove dupes
|
||||
instances.sort_unstable();
|
||||
instances.dedup();
|
||||
if let Some(allowed) = allowed.as_ref() {
|
||||
linked.extend_from_slice(allowed);
|
||||
}
|
||||
|
||||
Ok(instances)
|
||||
if let Some(blocked) = blocked.as_ref() {
|
||||
linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname()));
|
||||
}
|
||||
|
||||
// Sort and remove dupes
|
||||
linked.sort_unstable();
|
||||
linked.dedup();
|
||||
|
||||
Ok(Some(FederatedInstances {
|
||||
linked,
|
||||
allowed,
|
||||
blocked,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn match_websocket_operation(
|
||||
|
@ -223,17 +270,17 @@ pub async fn match_websocket_operation(
|
|||
UserOperation::Login => do_websocket_operation::<Login>(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::GetUserDetails => {
|
||||
do_websocket_operation::<GetUserDetails>(context, id, op, data).await
|
||||
UserOperation::GetPersonDetails => {
|
||||
do_websocket_operation::<GetPersonDetails>(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::BanUser => do_websocket_operation::<BanUser>(context, id, op, data).await,
|
||||
UserOperation::GetUserMentions => {
|
||||
do_websocket_operation::<GetUserMentions>(context, id, op, data).await
|
||||
UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
|
||||
UserOperation::GetPersonMentions => {
|
||||
do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::MarkUserMentionAsRead => {
|
||||
do_websocket_operation::<MarkUserMentionAsRead>(context, id, op, data).await
|
||||
UserOperation::MarkPersonMentionAsRead => {
|
||||
do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::MarkAllAsRead => {
|
||||
do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
|
||||
|
@ -295,9 +342,6 @@ pub async fn match_websocket_operation(
|
|||
UserOperation::TransferSite => {
|
||||
do_websocket_operation::<TransferSite>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::ListCategories => {
|
||||
do_websocket_operation::<ListCategories>(context, id, op, data).await
|
||||
}
|
||||
|
||||
// Community ops
|
||||
UserOperation::GetCommunity => {
|
||||
|
@ -434,7 +478,11 @@ pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyEr
|
|||
pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
|
||||
// Make a temp file path
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
|
||||
let file_path = format!(
|
||||
"{}/lemmy_espeak_{}.wav",
|
||||
env::temp_dir().to_string_lossy(),
|
||||
&uuid
|
||||
);
|
||||
|
||||
// Write the wav file
|
||||
Command::new("espeak")
|
||||
|
@ -455,9 +503,81 @@ pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
|
|||
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)]
|
||||
mod tests {
|
||||
use crate::captcha_espeak_wav_base64;
|
||||
use crate::{captcha_espeak_wav_base64, check_validator_time};
|
||||
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]
|
||||
fn test_espeak() {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,15 +1,15 @@
|
|||
use crate::{
|
||||
check_community_ban,
|
||||
check_downvotes_enabled,
|
||||
check_optional_url,
|
||||
collect_moderated_communities,
|
||||
get_user_from_jwt,
|
||||
get_user_from_jwt_opt,
|
||||
get_local_user_view_from_jwt,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
is_mod_or_admin,
|
||||
Perform,
|
||||
};
|
||||
use actix_web::web::Data;
|
||||
use lemmy_apub::{ApubLikeableType, ApubObjectType};
|
||||
use lemmy_api_structs::{blocking, post::*};
|
||||
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
|
||||
use lemmy_db_queries::{
|
||||
source::post::Post_,
|
||||
Crud,
|
||||
|
@ -36,12 +36,10 @@ use lemmy_db_views_actor::{
|
|||
community_moderator_view::CommunityModeratorView,
|
||||
community_view::CommunityView,
|
||||
};
|
||||
use lemmy_structs::{blocking, post::*};
|
||||
use lemmy_utils::{
|
||||
apub::{make_apub_endpoint, EndpointType},
|
||||
request::fetch_iframely_and_pictrs_data,
|
||||
utils::{check_slurs, check_slurs_opt, is_valid_post_title},
|
||||
APIError,
|
||||
ApiError,
|
||||
ConnectionId,
|
||||
LemmyError,
|
||||
};
|
||||
|
@ -62,29 +60,28 @@ impl Perform for CreatePost {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &CreatePost = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
check_slurs(&data.name)?;
|
||||
check_slurs_opt(&data.body)?;
|
||||
|
||||
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(user.id, data.community_id, context.pool()).await?;
|
||||
|
||||
check_optional_url(&Some(data.url.to_owned()))?;
|
||||
check_community_ban(local_user_view.person.id, data.community_id, context.pool()).await?;
|
||||
|
||||
// Fetch Iframely and pictrs cached image
|
||||
let data_url = data.url.as_ref();
|
||||
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
||||
fetch_iframely_and_pictrs_data(context.client(), data.url.to_owned()).await;
|
||||
fetch_iframely_and_pictrs_data(context.client(), data_url).await;
|
||||
|
||||
let post_form = PostForm {
|
||||
name: data.name.trim().to_owned(),
|
||||
url: data.url.to_owned(),
|
||||
url: data_url.map(|u| u.to_owned().into()),
|
||||
body: data.body.to_owned(),
|
||||
community_id: data.community_id,
|
||||
creator_id: user.id,
|
||||
creator_id: local_user_view.person.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
nsfw: data.nsfw,
|
||||
|
@ -94,7 +91,7 @@ impl Perform for CreatePost {
|
|||
embed_title: iframely_title,
|
||||
embed_description: iframely_description,
|
||||
embed_html: iframely_html,
|
||||
thumbnail_url: pictrs_thumbnail,
|
||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||
ap_id: None,
|
||||
local: true,
|
||||
published: None,
|
||||
|
@ -110,47 +107,50 @@ impl Perform for CreatePost {
|
|||
"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 updated_post = match blocking(context.pool(), move |conn| {
|
||||
let apub_id =
|
||||
make_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string()).to_string();
|
||||
Post::update_ap_id(conn, inserted_post_id, apub_id)
|
||||
let updated_post = match blocking(context.pool(), move |conn| -> Result<Post, LemmyError> {
|
||||
let apub_id = generate_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string())?;
|
||||
Ok(Post::update_ap_id(conn, inserted_post_id, apub_id)?)
|
||||
})
|
||||
.await?
|
||||
{
|
||||
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.send_create(&user, context).await?;
|
||||
updated_post
|
||||
.send_create(&local_user_view.person, context)
|
||||
.await?;
|
||||
|
||||
// They like their own post by default
|
||||
let like_form = PostLikeForm {
|
||||
post_id: inserted_post.id,
|
||||
user_id: user.id,
|
||||
person_id: local_user_view.person.id,
|
||||
score: 1,
|
||||
};
|
||||
|
||||
let like = move |conn: &'_ _| PostLike::like(conn, &like_form);
|
||||
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.send_like(&user, context).await?;
|
||||
updated_post
|
||||
.send_like(&local_user_view.person, context)
|
||||
.await?;
|
||||
|
||||
// Refetch the view
|
||||
let inserted_post_id = inserted_post.id;
|
||||
let post_view = match blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, inserted_post_id, Some(user.id))
|
||||
PostView::read(conn, inserted_post_id, Some(local_user_view.person.id))
|
||||
})
|
||||
.await?
|
||||
{
|
||||
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 };
|
||||
|
@ -175,23 +175,23 @@ impl Perform for GetPost {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetPostResponse, LemmyError> {
|
||||
let data: &GetPost = &self;
|
||||
let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let user_id = user.map(|u| u.id);
|
||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let person_id = local_user_view.map(|u| u.person.id);
|
||||
|
||||
let id = data.id;
|
||||
let post_view = match blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, id, user_id)
|
||||
PostView::read(conn, id, person_id)
|
||||
})
|
||||
.await?
|
||||
{
|
||||
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 comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.my_user_id(user_id)
|
||||
.my_person_id(person_id)
|
||||
.post_id(id)
|
||||
.limit(9999)
|
||||
.list()
|
||||
|
@ -206,12 +206,12 @@ impl Perform for GetPost {
|
|||
|
||||
// Necessary for the sidebar
|
||||
let community_view = match blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, user_id)
|
||||
CommunityView::read(conn, community_id, person_id)
|
||||
})
|
||||
.await?
|
||||
{
|
||||
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
|
||||
|
@ -241,15 +241,15 @@ impl Perform for GetPosts {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetPostsResponse, LemmyError> {
|
||||
let data: &GetPosts = &self;
|
||||
let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
|
||||
let user_id = match &user {
|
||||
Some(user) => Some(user.id),
|
||||
let person_id = match &local_user_view {
|
||||
Some(uv) => Some(uv.person.id),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let show_nsfw = match &user {
|
||||
Some(user) => user.show_nsfw,
|
||||
let show_nsfw = match &local_user_view {
|
||||
Some(uv) => uv.local_user.show_nsfw,
|
||||
None => false,
|
||||
};
|
||||
|
||||
|
@ -267,7 +267,7 @@ impl Perform for GetPosts {
|
|||
.show_nsfw(show_nsfw)
|
||||
.community_id(community_id)
|
||||
.community_name(community_name)
|
||||
.my_user_id(user_id)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
|
@ -275,7 +275,7 @@ impl Perform for GetPosts {
|
|||
.await?
|
||||
{
|
||||
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 })
|
||||
|
@ -292,7 +292,7 @@ impl Perform for CreatePostLike {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &CreatePostLike = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
// Don't do a downvote if site has downvotes disabled
|
||||
check_downvotes_enabled(data.score, context.pool()).await?;
|
||||
|
@ -301,18 +301,18 @@ impl Perform for CreatePostLike {
|
|||
let post_id = data.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
check_community_ban(user.id, post.community_id, context.pool()).await?;
|
||||
check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?;
|
||||
|
||||
let like_form = PostLikeForm {
|
||||
post_id: data.post_id,
|
||||
user_id: user.id,
|
||||
person_id: local_user_view.person.id,
|
||||
score: data.score,
|
||||
};
|
||||
|
||||
// Remove any likes first
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
PostLike::remove(conn, user_id, post_id)
|
||||
PostLike::remove(conn, person_id, post_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -322,27 +322,29 @@ impl Perform for CreatePostLike {
|
|||
let like_form2 = like_form.clone();
|
||||
let like = move |conn: &'_ _| PostLike::like(conn, &like_form2);
|
||||
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 {
|
||||
post.send_like(&user, context).await?;
|
||||
post.send_like(&local_user_view.person, context).await?;
|
||||
} else if like_form.score == -1 {
|
||||
post.send_dislike(&user, context).await?;
|
||||
post.send_dislike(&local_user_view.person, context).await?;
|
||||
}
|
||||
} else {
|
||||
post.send_undo_like(&user, context).await?;
|
||||
post
|
||||
.send_undo_like(&local_user_view.person, context)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let post_id = data.post_id;
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_view = match blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(user_id))
|
||||
PostView::read(conn, post_id, Some(person_id))
|
||||
})
|
||||
.await?
|
||||
{
|
||||
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 };
|
||||
|
@ -367,32 +369,38 @@ impl Perform for EditPost {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &EditPost = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
check_slurs(&data.name)?;
|
||||
check_slurs_opt(&data.body)?;
|
||||
|
||||
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 orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
orig_post.community_id,
|
||||
context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Verify that only the creator can edit
|
||||
if !Post::is_post_creator(user.id, orig_post.creator_id) {
|
||||
return Err(APIError::err("no_post_edit_allowed").into());
|
||||
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
|
||||
return Err(ApiError::err("no_post_edit_allowed").into());
|
||||
}
|
||||
|
||||
// Fetch Iframely and Pictrs cached image
|
||||
let data_url = data.url.as_ref();
|
||||
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
||||
fetch_iframely_and_pictrs_data(context.client(), data.url.to_owned()).await;
|
||||
fetch_iframely_and_pictrs_data(context.client(), data_url).await;
|
||||
|
||||
let post_form = PostForm {
|
||||
name: data.name.trim().to_owned(),
|
||||
url: data.url.to_owned(),
|
||||
url: data_url.map(|u| u.to_owned().into()),
|
||||
body: data.body.to_owned(),
|
||||
nsfw: data.nsfw,
|
||||
creator_id: orig_post.creator_id.to_owned(),
|
||||
|
@ -405,7 +413,7 @@ impl Perform for EditPost {
|
|||
embed_title: iframely_title,
|
||||
embed_description: iframely_description,
|
||||
embed_html: iframely_html,
|
||||
thumbnail_url: pictrs_thumbnail,
|
||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||
ap_id: Some(orig_post.ap_id),
|
||||
local: orig_post.local,
|
||||
published: None,
|
||||
|
@ -425,16 +433,18 @@ impl Perform for EditPost {
|
|||
"couldnt_update_post"
|
||||
};
|
||||
|
||||
return Err(APIError::err(err_type).into());
|
||||
return Err(ApiError::err(err_type).into());
|
||||
}
|
||||
};
|
||||
|
||||
// Send apub update
|
||||
updated_post.send_update(&user, context).await?;
|
||||
updated_post
|
||||
.send_update(&local_user_view.person, context)
|
||||
.await?;
|
||||
|
||||
let post_id = data.post_id;
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(user.id))
|
||||
PostView::read(conn, post_id, Some(local_user_view.person.id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -460,16 +470,21 @@ impl Perform for DeletePost {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &DeletePost = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let post_id = data.post_id;
|
||||
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
orig_post.community_id,
|
||||
context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Verify that only the creator can delete
|
||||
if !Post::is_post_creator(user.id, orig_post.creator_id) {
|
||||
return Err(APIError::err("no_post_edit_allowed").into());
|
||||
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
|
||||
return Err(ApiError::err("no_post_edit_allowed").into());
|
||||
}
|
||||
|
||||
// Update the post
|
||||
|
@ -482,15 +497,19 @@ impl Perform for DeletePost {
|
|||
|
||||
// apub updates
|
||||
if deleted {
|
||||
updated_post.send_delete(&user, context).await?;
|
||||
updated_post
|
||||
.send_delete(&local_user_view.person, context)
|
||||
.await?;
|
||||
} else {
|
||||
updated_post.send_undo_delete(&user, context).await?;
|
||||
updated_post
|
||||
.send_undo_delete(&local_user_view.person, context)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Refetch the post
|
||||
let post_id = data.post_id;
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(user.id))
|
||||
PostView::read(conn, post_id, Some(local_user_view.person.id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -516,15 +535,25 @@ impl Perform for RemovePost {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &RemovePost = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let post_id = data.post_id;
|
||||
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
orig_post.community_id,
|
||||
context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Verify that only the mods can remove
|
||||
is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
|
||||
is_mod_or_admin(
|
||||
context.pool(),
|
||||
local_user_view.person.id,
|
||||
orig_post.community_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Update the post
|
||||
let post_id = data.post_id;
|
||||
|
@ -536,7 +565,7 @@ impl Perform for RemovePost {
|
|||
|
||||
// Mod tables
|
||||
let form = ModRemovePostForm {
|
||||
mod_user_id: user.id,
|
||||
mod_person_id: local_user_view.person.id,
|
||||
post_id: data.post_id,
|
||||
removed: Some(removed),
|
||||
reason: data.reason.to_owned(),
|
||||
|
@ -548,16 +577,20 @@ impl Perform for RemovePost {
|
|||
|
||||
// apub updates
|
||||
if removed {
|
||||
updated_post.send_remove(&user, context).await?;
|
||||
updated_post
|
||||
.send_remove(&local_user_view.person, context)
|
||||
.await?;
|
||||
} else {
|
||||
updated_post.send_undo_remove(&user, context).await?;
|
||||
updated_post
|
||||
.send_undo_remove(&local_user_view.person, context)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Refetch the post
|
||||
let post_id = data.post_id;
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(user_id))
|
||||
PostView::read(conn, post_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -583,15 +616,25 @@ impl Perform for LockPost {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &LockPost = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let post_id = data.post_id;
|
||||
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
orig_post.community_id,
|
||||
context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Verify that only the mods can lock
|
||||
is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
|
||||
is_mod_or_admin(
|
||||
context.pool(),
|
||||
local_user_view.person.id,
|
||||
orig_post.community_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Update the post
|
||||
let post_id = data.post_id;
|
||||
|
@ -603,19 +646,21 @@ impl Perform for LockPost {
|
|||
|
||||
// Mod tables
|
||||
let form = ModLockPostForm {
|
||||
mod_user_id: user.id,
|
||||
mod_person_id: local_user_view.person.id,
|
||||
post_id: data.post_id,
|
||||
locked: Some(locked),
|
||||
};
|
||||
blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
|
||||
|
||||
// apub updates
|
||||
updated_post.send_update(&user, context).await?;
|
||||
updated_post
|
||||
.send_update(&local_user_view.person, context)
|
||||
.await?;
|
||||
|
||||
// Refetch the post
|
||||
let post_id = data.post_id;
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(user.id))
|
||||
PostView::read(conn, post_id, Some(local_user_view.person.id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -641,15 +686,25 @@ impl Perform for StickyPost {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &StickyPost = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let post_id = data.post_id;
|
||||
let orig_post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
check_community_ban(user.id, orig_post.community_id, context.pool()).await?;
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
orig_post.community_id,
|
||||
context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Verify that only the mods can sticky
|
||||
is_mod_or_admin(context.pool(), user.id, orig_post.community_id).await?;
|
||||
is_mod_or_admin(
|
||||
context.pool(),
|
||||
local_user_view.person.id,
|
||||
orig_post.community_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Update the post
|
||||
let post_id = data.post_id;
|
||||
|
@ -661,7 +716,7 @@ impl Perform for StickyPost {
|
|||
|
||||
// Mod tables
|
||||
let form = ModStickyPostForm {
|
||||
mod_user_id: user.id,
|
||||
mod_person_id: local_user_view.person.id,
|
||||
post_id: data.post_id,
|
||||
stickied: Some(stickied),
|
||||
};
|
||||
|
@ -672,12 +727,14 @@ impl Perform for StickyPost {
|
|||
|
||||
// Apub updates
|
||||
// TODO stickied should pry work like locked for ease of use
|
||||
updated_post.send_update(&user, context).await?;
|
||||
updated_post
|
||||
.send_update(&local_user_view.person, context)
|
||||
.await?;
|
||||
|
||||
// Refetch the post
|
||||
let post_id = data.post_id;
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(user.id))
|
||||
PostView::read(conn, post_id, Some(local_user_view.person.id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -703,29 +760,29 @@ impl Perform for SavePost {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<PostResponse, LemmyError> {
|
||||
let data: &SavePost = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let post_saved_form = PostSavedForm {
|
||||
post_id: data.post_id,
|
||||
user_id: user.id,
|
||||
person_id: local_user_view.person.id,
|
||||
};
|
||||
|
||||
if data.save {
|
||||
let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form);
|
||||
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 {
|
||||
let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form);
|
||||
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 user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(user_id))
|
||||
PostView::read(conn, post_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -744,28 +801,28 @@ impl Perform for CreatePostReport {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CreatePostReportResponse, LemmyError> {
|
||||
let data: &CreatePostReport = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
// check size of report and check for whitespace
|
||||
let reason = data.reason.trim();
|
||||
if reason.is_empty() {
|
||||
return Err(APIError::err("report_reason_required").into());
|
||||
return Err(ApiError::err("report_reason_required").into());
|
||||
}
|
||||
if reason.len() > 1000 {
|
||||
return Err(APIError::err("report_too_long").into());
|
||||
if reason.chars().count() > 1000 {
|
||||
return Err(ApiError::err("report_too_long").into());
|
||||
}
|
||||
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_id = data.post_id;
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(&conn, post_id, None)
|
||||
})
|
||||
.await??;
|
||||
|
||||
check_community_ban(user_id, post_view.community.id, context.pool()).await?;
|
||||
check_community_ban(person_id, post_view.community.id, context.pool()).await?;
|
||||
|
||||
let report_form = PostReportForm {
|
||||
creator_id: user_id,
|
||||
creator_id: person_id,
|
||||
post_id,
|
||||
original_post_name: post_view.post.name,
|
||||
original_post_url: post_view.post.url,
|
||||
|
@ -779,7 +836,7 @@ impl Perform for CreatePostReport {
|
|||
.await?
|
||||
{
|
||||
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 };
|
||||
|
@ -787,7 +844,7 @@ impl Perform for CreatePostReport {
|
|||
context.chat_server().do_send(SendUserRoomMessage {
|
||||
op: UserOperation::CreatePostReport,
|
||||
response: res.clone(),
|
||||
recipient_id: user.id,
|
||||
local_recipient_id: local_user_view.local_user.id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
|
@ -813,7 +870,7 @@ impl Perform for ResolvePostReport {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<ResolvePostReportResponse, LemmyError> {
|
||||
let data: &ResolvePostReport = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let report_id = data.report_id;
|
||||
let report = blocking(context.pool(), move |conn| {
|
||||
|
@ -821,15 +878,15 @@ impl Perform for ResolvePostReport {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let user_id = user.id;
|
||||
is_mod_or_admin(context.pool(), user_id, report.community.id).await?;
|
||||
let person_id = local_user_view.person.id;
|
||||
is_mod_or_admin(context.pool(), person_id, report.community.id).await?;
|
||||
|
||||
let resolved = data.resolved;
|
||||
let resolve_fun = move |conn: &'_ _| {
|
||||
if resolved {
|
||||
PostReport::resolve(conn, report_id, user_id)
|
||||
PostReport::resolve(conn, report_id, person_id)
|
||||
} else {
|
||||
PostReport::unresolve(conn, report_id, user_id)
|
||||
PostReport::unresolve(conn, report_id, person_id)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -839,7 +896,7 @@ impl Perform for ResolvePostReport {
|
|||
};
|
||||
|
||||
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 {
|
||||
|
@ -865,12 +922,12 @@ impl Perform for ListPostReports {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<ListPostReportsResponse, LemmyError> {
|
||||
let data: &ListPostReports = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
let user_id = user.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_id = data.community;
|
||||
let community_ids =
|
||||
collect_moderated_communities(user_id, community_id, context.pool()).await?;
|
||||
collect_moderated_communities(person_id, community_id, context.pool()).await?;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
|
@ -888,7 +945,7 @@ impl Perform for ListPostReports {
|
|||
context.chat_server().do_send(SendUserRoomMessage {
|
||||
op: UserOperation::ListPostReports,
|
||||
response: res.clone(),
|
||||
recipient_id: user.id,
|
||||
local_recipient_id: local_user_view.local_user.id,
|
||||
websocket_id,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use crate::Perform;
|
||||
use actix_web::{error::ErrorBadRequest, *};
|
||||
use lemmy_api::Perform;
|
||||
use lemmy_structs::{comment::*, community::*, post::*, site::*, user::*, websocket::*};
|
||||
use lemmy_api_structs::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
|
||||
use lemmy_utils::rate_limit::RateLimit;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use lemmy_websocket::{routes::chat_route, LemmyContext};
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||
cfg.service(
|
||||
web::scope("/api/v2")
|
||||
// Websockets
|
||||
.service(web::resource("/ws").to(super::websocket::chat_route))
|
||||
.service(web::resource("/ws").to(chat_route))
|
||||
// Site
|
||||
.service(
|
||||
web::scope("/site")
|
||||
|
@ -22,11 +22,6 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
|||
.route("/config", web::get().to(route_get::<GetSiteConfig>))
|
||||
.route("/config", web::put().to(route_post::<SaveSiteConfig>)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/categories")
|
||||
.wrap(rate_limit.message())
|
||||
.route(web::get().to(route_get::<ListCategories>)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/modlog")
|
||||
.wrap(rate_limit.message())
|
||||
|
@ -142,11 +137,11 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
|||
.service(
|
||||
web::scope("/user")
|
||||
.wrap(rate_limit.message())
|
||||
.route("", web::get().to(route_get::<GetUserDetails>))
|
||||
.route("/mention", web::get().to(route_get::<GetUserMentions>))
|
||||
.route("", web::get().to(route_get::<GetPersonDetails>))
|
||||
.route("/mention", web::get().to(route_get::<GetPersonMentions>))
|
||||
.route(
|
||||
"/mention/mark_as_read",
|
||||
web::post().to(route_post::<MarkUserMentionAsRead>),
|
||||
web::post().to(route_post::<MarkPersonMentionAsRead>),
|
||||
)
|
||||
.route("/replies", web::get().to(route_get::<GetReplies>))
|
||||
.route(
|
||||
|
@ -155,7 +150,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
|||
)
|
||||
.route("/join", web::post().to(route_post::<UserJoin>))
|
||||
// Admin action. I don't like that it's in /user
|
||||
.route("/ban", web::post().to(route_post::<BanUser>))
|
||||
.route("/ban", web::post().to(route_post::<BanPerson>))
|
||||
// Account actions. I don't like that they're in /user maybe /accounts
|
||||
.route("/login", web::post().to(route_post::<Login>))
|
||||
.route("/get_captcha", web::get().to(route_get::<GetCaptcha>))
|
|
@ -1,19 +1,19 @@
|
|||
use crate::{
|
||||
get_user_from_jwt,
|
||||
get_user_from_jwt_opt,
|
||||
get_user_safe_settings_from_jwt,
|
||||
get_user_safe_settings_from_jwt_opt,
|
||||
build_federated_instances,
|
||||
get_local_user_settings_view_from_jwt,
|
||||
get_local_user_settings_view_from_jwt_opt,
|
||||
get_local_user_view_from_jwt,
|
||||
get_local_user_view_from_jwt_opt,
|
||||
is_admin,
|
||||
linked_instances,
|
||||
version,
|
||||
Perform,
|
||||
};
|
||||
use actix_web::web::Data;
|
||||
use anyhow::Context;
|
||||
use lemmy_api_structs::{blocking, person::Register, site::*};
|
||||
use lemmy_apub::fetcher::search::search_by_apub_id;
|
||||
use lemmy_db_queries::{
|
||||
diesel_option_overwrite,
|
||||
source::{category::Category_, site::Site_},
|
||||
diesel_option_overwrite_to_url,
|
||||
source::site::Site_,
|
||||
Crud,
|
||||
SearchType,
|
||||
SortType,
|
||||
|
@ -21,7 +21,6 @@ use lemmy_db_queries::{
|
|||
use lemmy_db_schema::{
|
||||
naive_now,
|
||||
source::{
|
||||
category::Category,
|
||||
moderator::*,
|
||||
site::{Site, *},
|
||||
},
|
||||
|
@ -33,7 +32,7 @@ use lemmy_db_views::{
|
|||
};
|
||||
use lemmy_db_views_actor::{
|
||||
community_view::CommunityQueryBuilder,
|
||||
user_view::{UserQueryBuilder, UserViewSafe},
|
||||
person_view::{PersonQueryBuilder, PersonViewSafe},
|
||||
};
|
||||
use lemmy_db_views_moderator::{
|
||||
mod_add_community_view::ModAddCommunityView,
|
||||
|
@ -46,12 +45,12 @@ use lemmy_db_views_moderator::{
|
|||
mod_remove_post_view::ModRemovePostView,
|
||||
mod_sticky_post_view::ModStickyPostView,
|
||||
};
|
||||
use lemmy_structs::{blocking, site::*, user::Register};
|
||||
use lemmy_utils::{
|
||||
location_info,
|
||||
settings::Settings,
|
||||
settings::structs::Settings,
|
||||
utils::{check_slurs, check_slurs_opt},
|
||||
APIError,
|
||||
version,
|
||||
ApiError,
|
||||
ConnectionId,
|
||||
LemmyError,
|
||||
};
|
||||
|
@ -63,24 +62,6 @@ use lemmy_websocket::{
|
|||
use log::{debug, info};
|
||||
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)]
|
||||
impl Perform for GetModlog {
|
||||
type Response = GetModlogResponse;
|
||||
|
@ -93,36 +74,36 @@ impl Perform for GetModlog {
|
|||
let data: &GetModlog = &self;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let mod_user_id = data.mod_user_id;
|
||||
let mod_person_id = data.mod_person_id;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let removed_posts = blocking(context.pool(), move |conn| {
|
||||
ModRemovePostView::list(conn, community_id, mod_user_id, page, limit)
|
||||
ModRemovePostView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let locked_posts = blocking(context.pool(), move |conn| {
|
||||
ModLockPostView::list(conn, community_id, mod_user_id, page, limit)
|
||||
ModLockPostView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let stickied_posts = blocking(context.pool(), move |conn| {
|
||||
ModStickyPostView::list(conn, community_id, mod_user_id, page, limit)
|
||||
ModStickyPostView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let removed_comments = blocking(context.pool(), move |conn| {
|
||||
ModRemoveCommentView::list(conn, community_id, mod_user_id, page, limit)
|
||||
ModRemoveCommentView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let banned_from_community = blocking(context.pool(), move |conn| {
|
||||
ModBanFromCommunityView::list(conn, community_id, mod_user_id, page, limit)
|
||||
ModBanFromCommunityView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let added_to_community = blocking(context.pool(), move |conn| {
|
||||
ModAddCommunityView::list(conn, community_id, mod_user_id, page, limit)
|
||||
ModAddCommunityView::list(conn, community_id, mod_person_id, page, limit)
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -130,9 +111,9 @@ impl Perform for GetModlog {
|
|||
let (removed_communities, banned, added) = if data.community_id.is_none() {
|
||||
blocking(context.pool(), move |conn| {
|
||||
Ok((
|
||||
ModRemoveCommunityView::list(conn, mod_user_id, page, limit)?,
|
||||
ModBanView::list(conn, mod_user_id, page, limit)?,
|
||||
ModAddView::list(conn, mod_user_id, page, limit)?,
|
||||
ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?,
|
||||
ModBanView::list(conn, mod_person_id, page, limit)?,
|
||||
ModAddView::list(conn, mod_person_id, page, limit)?,
|
||||
)) as Result<_, LemmyError>
|
||||
})
|
||||
.await??
|
||||
|
@ -168,23 +149,23 @@ impl Perform for CreateSite {
|
|||
|
||||
let read_site = move |conn: &'_ _| Site::read_simple(conn);
|
||||
if blocking(context.pool(), read_site).await?.is_ok() {
|
||||
return Err(APIError::err("site_already_exists").into());
|
||||
return Err(ApiError::err("site_already_exists").into());
|
||||
};
|
||||
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
check_slurs(&data.name)?;
|
||||
check_slurs_opt(&data.description)?;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(context.pool(), user.id).await?;
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let site_form = SiteForm {
|
||||
name: data.name.to_owned(),
|
||||
description: data.description.to_owned(),
|
||||
icon: Some(data.icon.to_owned()),
|
||||
banner: Some(data.banner.to_owned()),
|
||||
creator_id: user.id,
|
||||
icon: Some(data.icon.to_owned().map(|url| url.into())),
|
||||
banner: Some(data.banner.to_owned().map(|url| url.into())),
|
||||
creator_id: local_user_view.person.id,
|
||||
enable_downvotes: data.enable_downvotes,
|
||||
open_registration: data.open_registration,
|
||||
enable_nsfw: data.enable_nsfw,
|
||||
|
@ -193,7 +174,7 @@ impl Perform for CreateSite {
|
|||
|
||||
let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
|
||||
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??;
|
||||
|
@ -211,18 +192,18 @@ impl Perform for EditSite {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<SiteResponse, LemmyError> {
|
||||
let data: &EditSite = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
check_slurs(&data.name)?;
|
||||
check_slurs_opt(&data.description)?;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(context.pool(), user.id).await?;
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let found_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
|
||||
|
||||
let icon = diesel_option_overwrite(&data.icon);
|
||||
let banner = diesel_option_overwrite(&data.banner);
|
||||
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||
|
||||
let site_form = SiteForm {
|
||||
name: data.name.to_owned(),
|
||||
|
@ -238,7 +219,7 @@ impl Perform for EditSite {
|
|||
|
||||
let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
|
||||
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??;
|
||||
|
@ -270,7 +251,7 @@ impl Perform for GetSite {
|
|||
Ok(site_view) => Some(site_view),
|
||||
// If the site isn't created yet, check the setup
|
||||
Err(_) => {
|
||||
if let Some(setup) = Settings::get().setup.as_ref() {
|
||||
if let Some(setup) = Settings::get().setup().as_ref() {
|
||||
let register = Register {
|
||||
username: setup.admin_username.to_owned(),
|
||||
email: setup.admin_email.to_owned(),
|
||||
|
@ -302,20 +283,20 @@ impl Perform for GetSite {
|
|||
}
|
||||
};
|
||||
|
||||
let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
|
||||
let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
|
||||
|
||||
// Make sure the site creator is the top admin
|
||||
if let Some(site_view) = site_view.to_owned() {
|
||||
let site_creator_id = site_view.creator.id;
|
||||
// TODO investigate why this is sometimes coming back null
|
||||
// Maybe user_.admin isn't being set to true?
|
||||
if let Some(creator_index) = admins.iter().position(|r| r.user.id == site_creator_id) {
|
||||
let creator_user = admins.remove(creator_index);
|
||||
admins.insert(0, creator_user);
|
||||
if let Some(creator_index) = admins.iter().position(|r| r.person.id == site_creator_id) {
|
||||
let creator_person = admins.remove(creator_index);
|
||||
admins.insert(0, creator_person);
|
||||
}
|
||||
}
|
||||
|
||||
let banned = blocking(context.pool(), move |conn| UserViewSafe::banned(conn)).await??;
|
||||
let banned = blocking(context.pool(), move |conn| PersonViewSafe::banned(conn)).await??;
|
||||
|
||||
let online = context
|
||||
.chat_server()
|
||||
|
@ -323,7 +304,8 @@ impl Perform for GetSite {
|
|||
.await
|
||||
.unwrap_or(1);
|
||||
|
||||
let my_user = get_user_safe_settings_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let my_user = get_local_user_settings_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let federated_instances = build_federated_instances(context.pool()).await?;
|
||||
|
||||
Ok(GetSiteResponse {
|
||||
site_view,
|
||||
|
@ -332,7 +314,7 @@ impl Perform for GetSite {
|
|||
online,
|
||||
version: version::VERSION.to_string(),
|
||||
my_user,
|
||||
federated_instances: linked_instances(context.pool()).await?,
|
||||
federated_instances,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -353,8 +335,8 @@ impl Perform for Search {
|
|||
Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
|
||||
}
|
||||
|
||||
let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let user_id = user.map(|u| u.id);
|
||||
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
|
||||
let person_id = local_user_view.map(|u| u.person.id);
|
||||
|
||||
let type_ = SearchType::from_str(&data.type_)?;
|
||||
|
||||
|
@ -379,7 +361,7 @@ impl Perform for Search {
|
|||
.show_nsfw(true)
|
||||
.community_id(community_id)
|
||||
.community_name(community_name)
|
||||
.my_user_id(user_id)
|
||||
.my_person_id(person_id)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
|
@ -392,7 +374,7 @@ impl Perform for Search {
|
|||
CommentQueryBuilder::create(&conn)
|
||||
.sort(&sort)
|
||||
.search_term(q)
|
||||
.my_user_id(user_id)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
|
@ -404,6 +386,7 @@ impl Perform for Search {
|
|||
CommunityQueryBuilder::create(conn)
|
||||
.sort(&sort)
|
||||
.search_term(q)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
|
@ -412,7 +395,7 @@ impl Perform for Search {
|
|||
}
|
||||
SearchType::Users => {
|
||||
users = blocking(context.pool(), move |conn| {
|
||||
UserQueryBuilder::create(conn)
|
||||
PersonQueryBuilder::create(conn)
|
||||
.sort(&sort)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
|
@ -428,7 +411,7 @@ impl Perform for Search {
|
|||
.show_nsfw(true)
|
||||
.community_id(community_id)
|
||||
.community_name(community_name)
|
||||
.my_user_id(user_id)
|
||||
.my_person_id(person_id)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
|
@ -443,7 +426,7 @@ impl Perform for Search {
|
|||
CommentQueryBuilder::create(conn)
|
||||
.sort(&sort)
|
||||
.search_term(q)
|
||||
.my_user_id(user_id)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
|
@ -457,6 +440,7 @@ impl Perform for Search {
|
|||
CommunityQueryBuilder::create(conn)
|
||||
.sort(&sort)
|
||||
.search_term(q)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
|
@ -467,7 +451,7 @@ impl Perform for Search {
|
|||
let sort = SortType::from_str(&data.sort)?;
|
||||
|
||||
users = blocking(context.pool(), move |conn| {
|
||||
UserQueryBuilder::create(conn)
|
||||
PersonQueryBuilder::create(conn)
|
||||
.sort(&sort)
|
||||
.search_term(q)
|
||||
.page(page)
|
||||
|
@ -481,6 +465,7 @@ impl Perform for Search {
|
|||
PostQueryBuilder::create(conn)
|
||||
.sort(&sort)
|
||||
.show_nsfw(true)
|
||||
.my_person_id(person_id)
|
||||
.community_id(community_id)
|
||||
.community_name(community_name)
|
||||
.url_search(q)
|
||||
|
@ -513,27 +498,27 @@ impl Perform for TransferSite {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetSiteResponse, LemmyError> {
|
||||
let data: &TransferSite = &self;
|
||||
let user = get_user_safe_settings_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
is_admin(context.pool(), user.id).await?;
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let read_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
|
||||
|
||||
// Make sure user is the creator
|
||||
if read_site.creator_id != user.id {
|
||||
return Err(APIError::err("not_an_admin").into());
|
||||
if read_site.creator_id != local_user_view.person.id {
|
||||
return Err(ApiError::err("not_an_admin").into());
|
||||
}
|
||||
|
||||
let new_creator_id = data.user_id;
|
||||
let new_creator_id = data.person_id;
|
||||
let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
|
||||
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
|
||||
let form = ModAddForm {
|
||||
mod_user_id: user.id,
|
||||
other_user_id: data.user_id,
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: data.person_id,
|
||||
removed: Some(false),
|
||||
};
|
||||
|
||||
|
@ -541,15 +526,18 @@ impl Perform for TransferSite {
|
|||
|
||||
let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
|
||||
|
||||
let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
|
||||
let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
|
||||
let creator_index = admins
|
||||
.iter()
|
||||
.position(|r| r.user.id == site_view.creator.id)
|
||||
.position(|r| r.person.id == site_view.creator.id)
|
||||
.context(location_info!())?;
|
||||
let creator_user = admins.remove(creator_index);
|
||||
admins.insert(0, creator_user);
|
||||
let creator_person = admins.remove(creator_index);
|
||||
admins.insert(0, creator_person);
|
||||
|
||||
let banned = blocking(context.pool(), move |conn| UserViewSafe::banned(conn)).await??;
|
||||
let banned = blocking(context.pool(), move |conn| PersonViewSafe::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 {
|
||||
site_view: Some(site_view),
|
||||
|
@ -557,8 +545,8 @@ impl Perform for TransferSite {
|
|||
banned,
|
||||
online: 0,
|
||||
version: version::VERSION.to_string(),
|
||||
my_user: Some(user),
|
||||
federated_instances: linked_instances(context.pool()).await?,
|
||||
my_user,
|
||||
federated_instances,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -573,10 +561,10 @@ impl Perform for GetSiteConfig {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetSiteConfigResponse, LemmyError> {
|
||||
let data: &GetSiteConfig = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
// Only let admins read this
|
||||
is_admin(context.pool(), user.id).await?;
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let config_hjson = Settings::read_config_file()?;
|
||||
|
||||
|
@ -594,16 +582,15 @@ impl Perform for SaveSiteConfig {
|
|||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<GetSiteConfigResponse, LemmyError> {
|
||||
let data: &SaveSiteConfig = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
|
||||
|
||||
// Only let admins read this
|
||||
let user_id = user.id;
|
||||
is_admin(context.pool(), user_id).await?;
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// 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) {
|
||||
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 })
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
pub const VERSION: &str = "0.9.0-rc.12";
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{get_user_from_jwt, Perform};
|
||||
use crate::{get_local_user_view_from_jwt, Perform};
|
||||
use actix_web::web::Data;
|
||||
use lemmy_structs::websocket::*;
|
||||
use lemmy_api_structs::websocket::*;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{
|
||||
messages::{JoinCommunityRoom, JoinModRoom, JoinPostRoom, JoinUserRoom},
|
||||
|
@ -17,11 +17,11 @@ impl Perform for UserJoin {
|
|||
websocket_id: Option<ConnectionId>,
|
||||
) -> Result<UserJoinResponse, LemmyError> {
|
||||
let data: &UserJoin = &self;
|
||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||
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 {
|
||||
user_id: user.id,
|
||||
local_user_id: local_user_view.local_user.id,
|
||||
id: ws_id,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "lemmy_structs"
|
||||
name = "lemmy_api_structs"
|
||||
version = "0.1.0"
|
||||
authors = ["Felix Ableitner <me@nutomic.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "lemmy_structs"
|
||||
name = "lemmy_api_structs"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
lemmy_db_queries = { path = "../db_queries" }
|
||||
|
@ -15,9 +15,10 @@ 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.118", features = ["derive"] }
|
||||
log = "0.4.11"
|
||||
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.60", features = ["preserve_order"] }
|
||||
serde_json = { version = "1.0.61", features = ["preserve_order"] }
|
||||
url = "2.2.1"
|
|
@ -1,11 +1,12 @@
|
|||
use lemmy_db_schema::{CommentId, CommunityId, LocalUserId, PostId};
|
||||
use lemmy_db_views::{comment_report_view::CommentReportView, comment_view::CommentView};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateComment {
|
||||
pub content: String,
|
||||
pub parent_id: Option<i32>,
|
||||
pub post_id: i32,
|
||||
pub parent_id: Option<CommentId>,
|
||||
pub post_id: PostId,
|
||||
pub form_id: Option<String>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
@ -13,21 +14,21 @@ pub struct CreateComment {
|
|||
#[derive(Deserialize)]
|
||||
pub struct EditComment {
|
||||
pub content: String,
|
||||
pub comment_id: i32,
|
||||
pub comment_id: CommentId,
|
||||
pub form_id: Option<String>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DeleteComment {
|
||||
pub comment_id: i32,
|
||||
pub comment_id: CommentId,
|
||||
pub deleted: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RemoveComment {
|
||||
pub comment_id: i32,
|
||||
pub comment_id: CommentId,
|
||||
pub removed: bool,
|
||||
pub reason: Option<String>,
|
||||
pub auth: String,
|
||||
|
@ -35,14 +36,14 @@ pub struct RemoveComment {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MarkCommentAsRead {
|
||||
pub comment_id: i32,
|
||||
pub comment_id: CommentId,
|
||||
pub read: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SaveComment {
|
||||
pub comment_id: i32,
|
||||
pub comment_id: CommentId,
|
||||
pub save: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
@ -50,13 +51,13 @@ pub struct SaveComment {
|
|||
#[derive(Serialize, Clone)]
|
||||
pub struct CommentResponse {
|
||||
pub comment_view: CommentView,
|
||||
pub recipient_ids: Vec<i32>, // TODO another way to do this? Maybe a UserMention belongs to Comment
|
||||
pub recipient_ids: Vec<LocalUserId>,
|
||||
pub form_id: Option<String>, // An optional front end ID, to tell which is coming back
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateCommentLike {
|
||||
pub comment_id: i32,
|
||||
pub comment_id: CommentId,
|
||||
pub score: i16,
|
||||
pub auth: String,
|
||||
}
|
||||
|
@ -67,7 +68,7 @@ pub struct GetComments {
|
|||
pub sort: String,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
pub community_id: Option<i32>,
|
||||
pub community_id: Option<CommunityId>,
|
||||
pub community_name: Option<String>,
|
||||
pub auth: Option<String>,
|
||||
}
|
||||
|
@ -79,7 +80,7 @@ pub struct GetCommentsResponse {
|
|||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CreateCommentReport {
|
||||
pub comment_id: i32,
|
||||
pub comment_id: CommentId,
|
||||
pub reason: String,
|
||||
pub auth: String,
|
||||
}
|
||||
|
@ -108,7 +109,7 @@ pub struct ListCommentReports {
|
|||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
/// if no community is given, it returns reports for all communities moderated by the auth user
|
||||
pub community: Option<i32>,
|
||||
pub community: Option<CommunityId>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
use lemmy_db_schema::{CommunityId, PersonId};
|
||||
use lemmy_db_views_actor::{
|
||||
community_follower_view::CommunityFollowerView,
|
||||
community_moderator_view::CommunityModeratorView,
|
||||
community_view::CommunityView,
|
||||
user_view::UserViewSafe,
|
||||
person_view::PersonViewSafe,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetCommunity {
|
||||
pub id: Option<i32>,
|
||||
pub id: Option<CommunityId>,
|
||||
pub name: Option<String>,
|
||||
pub auth: Option<String>,
|
||||
}
|
||||
|
@ -27,7 +28,6 @@ pub struct CreateCommunity {
|
|||
pub description: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub banner: Option<String>,
|
||||
pub category_id: i32,
|
||||
pub nsfw: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ pub struct CommunityResponse {
|
|||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ListCommunities {
|
||||
pub type_: String,
|
||||
pub sort: String,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
|
@ -52,8 +53,8 @@ pub struct ListCommunitiesResponse {
|
|||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct BanFromCommunity {
|
||||
pub community_id: i32,
|
||||
pub user_id: i32,
|
||||
pub community_id: CommunityId,
|
||||
pub person_id: PersonId,
|
||||
pub ban: bool,
|
||||
pub remove_data: bool,
|
||||
pub reason: Option<String>,
|
||||
|
@ -63,14 +64,14 @@ pub struct BanFromCommunity {
|
|||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct BanFromCommunityResponse {
|
||||
pub user_view: UserViewSafe,
|
||||
pub person_view: PersonViewSafe,
|
||||
pub banned: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AddModToCommunity {
|
||||
pub community_id: i32,
|
||||
pub user_id: i32,
|
||||
pub community_id: CommunityId,
|
||||
pub person_id: PersonId,
|
||||
pub added: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
@ -82,26 +83,25 @@ pub struct AddModToCommunityResponse {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditCommunity {
|
||||
pub community_id: i32,
|
||||
pub community_id: CommunityId,
|
||||
pub title: String,
|
||||
pub description: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub banner: Option<String>,
|
||||
pub category_id: i32,
|
||||
pub nsfw: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DeleteCommunity {
|
||||
pub community_id: i32,
|
||||
pub community_id: CommunityId,
|
||||
pub deleted: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RemoveCommunity {
|
||||
pub community_id: i32,
|
||||
pub community_id: CommunityId,
|
||||
pub removed: bool,
|
||||
pub reason: Option<String>,
|
||||
pub expires: Option<i64>,
|
||||
|
@ -110,7 +110,7 @@ pub struct RemoveCommunity {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FollowCommunity {
|
||||
pub community_id: i32,
|
||||
pub community_id: CommunityId,
|
||||
pub follow: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ pub struct GetFollowedCommunitiesResponse {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct TransferCommunity {
|
||||
pub community_id: i32,
|
||||
pub user_id: i32,
|
||||
pub community_id: CommunityId,
|
||||
pub person_id: PersonId,
|
||||
pub auth: String,
|
||||
}
|
|
@ -1,28 +1,33 @@
|
|||
pub mod comment;
|
||||
pub mod community;
|
||||
pub mod person;
|
||||
pub mod post;
|
||||
pub mod site;
|
||||
pub mod user;
|
||||
pub mod websocket;
|
||||
|
||||
use diesel::PgConnection;
|
||||
use lemmy_db_queries::{source::user::User, Crud, DbPool};
|
||||
use lemmy_db_schema::source::{
|
||||
use lemmy_db_queries::{Crud, DbPool};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
comment::Comment,
|
||||
person::Person,
|
||||
person_mention::{PersonMention, PersonMentionForm},
|
||||
post::Post,
|
||||
user::User_,
|
||||
user_mention::{UserMention, UserMentionForm},
|
||||
},
|
||||
LocalUserId,
|
||||
};
|
||||
use lemmy_utils::{email::send_email, settings::Settings, utils::MentionData, LemmyError};
|
||||
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<String>,
|
||||
pub href: Option<Url>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub template: Option<String>,
|
||||
}
|
||||
|
@ -30,7 +35,7 @@ pub struct WebFingerLink {
|
|||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct WebFingerResponse {
|
||||
pub subject: String,
|
||||
pub aliases: Vec<String>,
|
||||
pub aliases: Vec<Url>,
|
||||
pub links: Vec<WebFingerLink>,
|
||||
}
|
||||
|
||||
|
@ -53,14 +58,13 @@ where
|
|||
pub async fn send_local_notifs(
|
||||
mentions: Vec<MentionData>,
|
||||
comment: Comment,
|
||||
user: &User_,
|
||||
person: Person,
|
||||
post: Post,
|
||||
pool: &DbPool,
|
||||
do_send_email: bool,
|
||||
) -> Result<Vec<i32>, LemmyError> {
|
||||
let user2 = user.clone();
|
||||
) -> Result<Vec<LocalUserId>, LemmyError> {
|
||||
let ids = blocking(pool, move |conn| {
|
||||
do_send_local_notifs(conn, &mentions, &comment, &user2, &post, do_send_email)
|
||||
do_send_local_notifs(conn, &mentions, &comment, &person, &post, do_send_email)
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
@ -71,40 +75,40 @@ fn do_send_local_notifs(
|
|||
conn: &PgConnection,
|
||||
mentions: &[MentionData],
|
||||
comment: &Comment,
|
||||
user: &User_,
|
||||
person: &Person,
|
||||
post: &Post,
|
||||
do_send_email: bool,
|
||||
) -> Vec<i32> {
|
||||
) -> 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(&user.name))
|
||||
.filter(|m| m.is_local() && m.name.ne(&person.name))
|
||||
.collect::<Vec<&MentionData>>()
|
||||
{
|
||||
if let Ok(mention_user) = User_::read_from_name(&conn, &mention.name) {
|
||||
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.id);
|
||||
recipient_ids.push(mention_user_view.local_user.id);
|
||||
|
||||
let user_mention_form = UserMentionForm {
|
||||
recipient_id: mention_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
|
||||
let _ = UserMention::create(&conn, &user_mention_form);
|
||||
PersonMention::create(&conn, &user_mention_form).ok();
|
||||
|
||||
// Send an email to those users that have notifications on
|
||||
if do_send_email && mention_user.send_notifications_to_email {
|
||||
// Send an email to those local users that have notifications on
|
||||
if do_send_email {
|
||||
send_email_to_user(
|
||||
mention_user,
|
||||
&mention_user_view,
|
||||
"Mentioned by",
|
||||
"User Mention",
|
||||
"Person Mention",
|
||||
&comment.content,
|
||||
)
|
||||
}
|
||||
|
@ -115,12 +119,20 @@ fn do_send_local_notifs(
|
|||
match comment.parent_id {
|
||||
Some(parent_id) => {
|
||||
if let Ok(parent_comment) = Comment::read(&conn, parent_id) {
|
||||
if parent_comment.creator_id != user.id {
|
||||
if let Ok(parent_user) = User_::read(&conn, parent_comment.creator_id) {
|
||||
recipient_ids.push(parent_user.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 && parent_user.send_notifications_to_email {
|
||||
send_email_to_user(parent_user, "Reply from", "Comment Reply", &comment.content)
|
||||
if do_send_email {
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
"Reply from",
|
||||
"Comment Reply",
|
||||
&comment.content,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,12 +140,17 @@ fn do_send_local_notifs(
|
|||
}
|
||||
// Its a post
|
||||
None => {
|
||||
if post.creator_id != user.id {
|
||||
if let Ok(parent_user) = User_::read(&conn, post.creator_id) {
|
||||
recipient_ids.push(parent_user.id);
|
||||
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 && parent_user.send_notifications_to_email {
|
||||
send_email_to_user(parent_user, "Reply from", "Post Reply", &comment.content)
|
||||
if do_send_email {
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
"Reply from",
|
||||
"Post Reply",
|
||||
&comment.content,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,26 +159,31 @@ fn do_send_local_notifs(
|
|||
recipient_ids
|
||||
}
|
||||
|
||||
pub fn send_email_to_user(user: User_, subject_text: &str, body_text: &str, comment_content: &str) {
|
||||
if user.banned {
|
||||
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) = user.email {
|
||||
if let Some(user_email) = &local_user_view.local_user.email {
|
||||
let subject = &format!(
|
||||
"{} - {} {}",
|
||||
subject_text,
|
||||
Settings::get().hostname,
|
||||
user.name,
|
||||
Settings::get().hostname(),
|
||||
local_user_view.person.name,
|
||||
);
|
||||
let html = &format!(
|
||||
"<h1>{}</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
|
||||
body_text,
|
||||
user.name,
|
||||
local_user_view.person.name,
|
||||
comment_content,
|
||||
Settings::get().get_protocol_and_hostname()
|
||||
);
|
||||
match send_email(subject, &user_email, &user.name, html) {
|
||||
match send_email(subject, &user_email, &local_user_view.person.name, html) {
|
||||
Ok(_o) => _o,
|
||||
Err(e) => error!("{}", e),
|
||||
};
|
|
@ -6,8 +6,8 @@ use lemmy_db_views::{
|
|||
use lemmy_db_views_actor::{
|
||||
community_follower_view::CommunityFollowerView,
|
||||
community_moderator_view::CommunityModeratorView,
|
||||
user_mention_view::UserMentionView,
|
||||
user_view::UserViewSafe,
|
||||
person_mention_view::PersonMentionView,
|
||||
person_view::PersonViewSafe,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -16,6 +16,7 @@ pub struct Login {
|
|||
pub username_or_email: String,
|
||||
pub password: String,
|
||||
}
|
||||
use lemmy_db_schema::{CommunityId, PersonId, PersonMentionId, PrivateMessageId};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Register {
|
||||
|
@ -45,11 +46,11 @@ pub struct CaptchaResponse {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SaveUserSettings {
|
||||
pub show_nsfw: bool,
|
||||
pub theme: String,
|
||||
pub default_sort_type: i16,
|
||||
pub default_listing_type: i16,
|
||||
pub lang: String,
|
||||
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 avatar: Option<String>,
|
||||
pub banner: Option<String>,
|
||||
pub preferred_username: Option<String>,
|
||||
|
@ -59,8 +60,8 @@ pub struct SaveUserSettings {
|
|||
pub new_password: Option<String>,
|
||||
pub new_password_verify: Option<String>,
|
||||
pub old_password: Option<String>,
|
||||
pub show_avatars: bool,
|
||||
pub send_notifications_to_email: bool,
|
||||
pub show_avatars: Option<bool>,
|
||||
pub send_notifications_to_email: Option<bool>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
|
@ -70,20 +71,20 @@ pub struct LoginResponse {
|
|||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetUserDetails {
|
||||
pub user_id: Option<i32>,
|
||||
pub struct GetPersonDetails {
|
||||
pub person_id: Option<PersonId>,
|
||||
pub username: Option<String>,
|
||||
pub sort: String,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
pub community_id: Option<i32>,
|
||||
pub community_id: Option<CommunityId>,
|
||||
pub saved_only: bool,
|
||||
pub auth: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GetUserDetailsResponse {
|
||||
pub user_view: UserViewSafe,
|
||||
pub struct GetPersonDetailsResponse {
|
||||
pub person_view: PersonViewSafe,
|
||||
pub follows: Vec<CommunityFollowerView>,
|
||||
pub moderates: Vec<CommunityModeratorView>,
|
||||
pub comments: Vec<CommentView>,
|
||||
|
@ -96,8 +97,8 @@ pub struct GetRepliesResponse {
|
|||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GetUserMentionsResponse {
|
||||
pub mentions: Vec<UserMentionView>,
|
||||
pub struct GetPersonMentionsResponse {
|
||||
pub mentions: Vec<PersonMentionView>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -107,19 +108,19 @@ pub struct MarkAllAsRead {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AddAdmin {
|
||||
pub user_id: i32,
|
||||
pub person_id: PersonId,
|
||||
pub added: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct AddAdminResponse {
|
||||
pub admins: Vec<UserViewSafe>,
|
||||
pub admins: Vec<PersonViewSafe>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct BanUser {
|
||||
pub user_id: i32,
|
||||
pub struct BanPerson {
|
||||
pub person_id: PersonId,
|
||||
pub ban: bool,
|
||||
pub remove_data: bool,
|
||||
pub reason: Option<String>,
|
||||
|
@ -128,8 +129,8 @@ pub struct BanUser {
|
|||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct BanUserResponse {
|
||||
pub user_view: UserViewSafe,
|
||||
pub struct BanPersonResponse {
|
||||
pub person_view: PersonViewSafe,
|
||||
pub banned: bool,
|
||||
}
|
||||
|
||||
|
@ -143,7 +144,7 @@ pub struct GetReplies {
|
|||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetUserMentions {
|
||||
pub struct GetPersonMentions {
|
||||
pub sort: String,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
|
@ -152,15 +153,15 @@ pub struct GetUserMentions {
|
|||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MarkUserMentionAsRead {
|
||||
pub user_mention_id: i32,
|
||||
pub struct MarkPersonMentionAsRead {
|
||||
pub person_mention_id: PersonMentionId,
|
||||
pub read: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct UserMentionResponse {
|
||||
pub user_mention_view: UserMentionView,
|
||||
pub struct PersonMentionResponse {
|
||||
pub person_mention_view: PersonMentionView,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -187,27 +188,27 @@ pub struct PasswordChange {
|
|||
#[derive(Deserialize)]
|
||||
pub struct CreatePrivateMessage {
|
||||
pub content: String,
|
||||
pub recipient_id: i32,
|
||||
pub recipient_id: PersonId,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditPrivateMessage {
|
||||
pub private_message_id: i32,
|
||||
pub private_message_id: PrivateMessageId,
|
||||
pub content: String,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DeletePrivateMessage {
|
||||
pub private_message_id: i32,
|
||||
pub private_message_id: PrivateMessageId,
|
||||
pub deleted: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MarkPrivateMessageAsRead {
|
||||
pub private_message_id: i32,
|
||||
pub private_message_id: PrivateMessageId,
|
||||
pub read: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
@ -232,13 +233,13 @@ pub struct PrivateMessageResponse {
|
|||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetReportCount {
|
||||
pub community: Option<i32>,
|
||||
pub community: Option<CommunityId>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct GetReportCountResponse {
|
||||
pub community: Option<i32>,
|
||||
pub community: Option<CommunityId>,
|
||||
pub comment_reports: i64,
|
||||
pub post_reports: i64,
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
use lemmy_db_schema::{CommunityId, PostId};
|
||||
use lemmy_db_views::{
|
||||
comment_view::CommentView,
|
||||
post_report_view::PostReportView,
|
||||
|
@ -8,14 +9,15 @@ use lemmy_db_views_actor::{
|
|||
community_view::CommunityView,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CreatePost {
|
||||
pub name: String,
|
||||
pub url: Option<String>,
|
||||
pub url: Option<Url>,
|
||||
pub body: Option<String>,
|
||||
pub nsfw: bool,
|
||||
pub community_id: i32,
|
||||
pub community_id: CommunityId,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
|
@ -26,7 +28,7 @@ pub struct PostResponse {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetPost {
|
||||
pub id: i32,
|
||||
pub id: PostId,
|
||||
pub auth: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -45,7 +47,7 @@ pub struct GetPosts {
|
|||
pub sort: String,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
pub community_id: Option<i32>,
|
||||
pub community_id: Option<CommunityId>,
|
||||
pub community_name: Option<String>,
|
||||
pub auth: Option<String>,
|
||||
}
|
||||
|
@ -57,16 +59,16 @@ pub struct GetPostsResponse {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreatePostLike {
|
||||
pub post_id: i32,
|
||||
pub post_id: PostId,
|
||||
pub score: i16,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditPost {
|
||||
pub post_id: i32,
|
||||
pub post_id: PostId,
|
||||
pub name: String,
|
||||
pub url: Option<String>,
|
||||
pub url: Option<Url>,
|
||||
pub body: Option<String>,
|
||||
pub nsfw: bool,
|
||||
pub auth: String,
|
||||
|
@ -74,14 +76,14 @@ pub struct EditPost {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DeletePost {
|
||||
pub post_id: i32,
|
||||
pub post_id: PostId,
|
||||
pub deleted: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RemovePost {
|
||||
pub post_id: i32,
|
||||
pub post_id: PostId,
|
||||
pub removed: bool,
|
||||
pub reason: Option<String>,
|
||||
pub auth: String,
|
||||
|
@ -89,28 +91,28 @@ pub struct RemovePost {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LockPost {
|
||||
pub post_id: i32,
|
||||
pub post_id: PostId,
|
||||
pub locked: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct StickyPost {
|
||||
pub post_id: i32,
|
||||
pub post_id: PostId,
|
||||
pub stickied: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SavePost {
|
||||
pub post_id: i32,
|
||||
pub post_id: PostId,
|
||||
pub save: bool,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CreatePostReport {
|
||||
pub post_id: i32,
|
||||
pub post_id: PostId,
|
||||
pub reason: String,
|
||||
pub auth: String,
|
||||
}
|
||||
|
@ -137,7 +139,7 @@ pub struct ResolvePostReportResponse {
|
|||
pub struct ListPostReports {
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
pub community: Option<i32>,
|
||||
pub community: Option<CommunityId>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
use lemmy_db_schema::source::{category::*, user::UserSafeSettings};
|
||||
use lemmy_db_views::{comment_view::CommentView, post_view::PostView, site_view::SiteView};
|
||||
use lemmy_db_views_actor::{community_view::CommunityView, user_view::UserViewSafe};
|
||||
use lemmy_db_schema::{CommunityId, PersonId};
|
||||
use lemmy_db_views::{
|
||||
comment_view::CommentView,
|
||||
local_user_view::LocalUserSettingsView,
|
||||
post_view::PostView,
|
||||
site_view::SiteView,
|
||||
};
|
||||
use lemmy_db_views_actor::{community_view::CommunityView, person_view::PersonViewSafe};
|
||||
use lemmy_db_views_moderator::{
|
||||
mod_add_community_view::ModAddCommunityView,
|
||||
mod_add_view::ModAddView,
|
||||
|
@ -13,20 +18,13 @@ use lemmy_db_views_moderator::{
|
|||
mod_sticky_post_view::ModStickyPostView,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListCategories {}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ListCategoriesResponse {
|
||||
pub categories: Vec<Category>,
|
||||
}
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Search {
|
||||
pub q: String,
|
||||
pub type_: String,
|
||||
pub community_id: Option<i32>,
|
||||
pub community_id: Option<CommunityId>,
|
||||
pub community_name: Option<String>,
|
||||
pub sort: String,
|
||||
pub page: Option<i64>,
|
||||
|
@ -40,13 +38,13 @@ pub struct SearchResponse {
|
|||
pub comments: Vec<CommentView>,
|
||||
pub posts: Vec<PostView>,
|
||||
pub communities: Vec<CommunityView>,
|
||||
pub users: Vec<UserViewSafe>,
|
||||
pub users: Vec<PersonViewSafe>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetModlog {
|
||||
pub mod_user_id: Option<i32>,
|
||||
pub community_id: Option<i32>,
|
||||
pub mod_person_id: Option<PersonId>,
|
||||
pub community_id: Option<CommunityId>,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
}
|
||||
|
@ -68,8 +66,8 @@ pub struct GetModlogResponse {
|
|||
pub struct CreateSite {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub banner: Option<String>,
|
||||
pub icon: Option<Url>,
|
||||
pub banner: Option<Url>,
|
||||
pub enable_downvotes: bool,
|
||||
pub open_registration: bool,
|
||||
pub enable_nsfw: bool,
|
||||
|
@ -101,17 +99,17 @@ pub struct SiteResponse {
|
|||
#[derive(Serialize)]
|
||||
pub struct GetSiteResponse {
|
||||
pub site_view: Option<SiteView>, // Because the site might not be set up yet
|
||||
pub admins: Vec<UserViewSafe>,
|
||||
pub banned: Vec<UserViewSafe>,
|
||||
pub admins: Vec<PersonViewSafe>,
|
||||
pub banned: Vec<PersonViewSafe>,
|
||||
pub online: usize,
|
||||
pub version: String,
|
||||
pub my_user: Option<UserSafeSettings>,
|
||||
pub federated_instances: Vec<String>,
|
||||
pub my_user: Option<LocalUserSettingsView>,
|
||||
pub federated_instances: Option<FederatedInstances>, // Federation may be disabled
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct TransferSite {
|
||||
pub user_id: i32,
|
||||
pub person_id: PersonId,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
|
@ -130,3 +128,10 @@ pub struct SaveSiteConfig {
|
|||
pub config_hjson: String,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FederatedInstances {
|
||||
pub linked: Vec<String>,
|
||||
pub allowed: Option<Vec<String>>,
|
||||
pub blocked: Option<Vec<String>>,
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
use lemmy_db_schema::{CommunityId, PostId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
@ -12,7 +13,7 @@ pub struct UserJoinResponse {
|
|||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CommunityJoin {
|
||||
pub community_id: i32,
|
||||
pub community_id: CommunityId,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
|
@ -22,7 +23,7 @@ pub struct CommunityJoinResponse {
|
|||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ModJoin {
|
||||
pub community_id: i32,
|
||||
pub community_id: CommunityId,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
|
@ -32,7 +33,7 @@ pub struct ModJoinResponse {
|
|||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct PostJoin {
|
||||
pub post_id: i32,
|
||||
pub post_id: PostId,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "lemmy_apub"
|
||||
version = "0.1.0"
|
||||
authors = ["Felix Ableitner <me@nutomic.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "lemmy_apub"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
lemmy_utils = { path = "../utils" }
|
||||
|
@ -14,39 +14,39 @@ lemmy_db_queries = { path = "../db_queries" }
|
|||
lemmy_db_schema = { path = "../db_schema" }
|
||||
lemmy_db_views = { path = "../db_views" }
|
||||
lemmy_db_views_actor = { path = "../db_views_actor" }
|
||||
lemmy_structs = { path = "../structs" }
|
||||
lemmy_api_structs = { path = "../api_structs" }
|
||||
lemmy_websocket = { path = "../websocket" }
|
||||
diesel = "1.4.5"
|
||||
activitystreams = "0.7.0-alpha.8"
|
||||
activitystreams = "0.7.0-alpha.10"
|
||||
activitystreams-ext = "0.1.0-alpha.2"
|
||||
bcrypt = "0.9.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
serde_json = { version = "1.0.60", features = ["preserve_order"] }
|
||||
serde = { version = "1.0.118", features = ["derive"] }
|
||||
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.11"
|
||||
rand = "0.8.0"
|
||||
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.0", features = ["serde"] }
|
||||
url = { version = "2.2.1", features = ["serde"] }
|
||||
percent-encoding = "2.1.0"
|
||||
openssl = "0.10.31"
|
||||
http = "0.2.2"
|
||||
openssl = "0.10.32"
|
||||
http = "0.2.3"
|
||||
http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
|
||||
http-signature-normalization-reqwest = { version = "0.1.3", default-features = false, features = ["sha-2"] }
|
||||
base64 = "0.13.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"
|
||||
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"
|
||||
anyhow = "1.0.36"
|
||||
thiserror = "1.0.22"
|
||||
anyhow = "1.0.38"
|
||||
thiserror = "1.0.23"
|
||||
background-jobs = "0.8.0"
|
||||
reqwest = { version = "0.10.10", features = ["json"] }
|
||||
backtrace = "0.3.55"
|
||||
backtrace = "0.3.56"
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, NoteExt};
|
||||
use crate::{activities::receive::get_actor_as_person, objects::FromApub, ActorType, NoteExt};
|
||||
use activitystreams::{
|
||||
activity::{ActorAndObjectRefExt, Create, Dislike, Like, Remove, Update},
|
||||
base::ExtendsExt,
|
||||
};
|
||||
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_schema::source::{
|
||||
comment::{Comment, CommentLike, CommentLikeForm},
|
||||
post::Post,
|
||||
};
|
||||
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_websocket::{messages::SendComment, LemmyContext, UserOperation};
|
||||
|
||||
|
@ -19,11 +19,11 @@ pub(crate) async fn receive_create_comment(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let user = get_actor_as_user(&create, context, request_counter).await?;
|
||||
let person = get_actor_as_person(&create, context, request_counter).await?;
|
||||
let note = NoteExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
|
||||
.context(location_info!())?;
|
||||
|
||||
let comment = Comment::from_apub(¬e, context, user.actor_id()?, request_counter).await?;
|
||||
let comment = Comment::from_apub(¬e, context, person.actor_id(), request_counter).await?;
|
||||
|
||||
let post_id = comment.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
@ -33,8 +33,15 @@ pub(crate) async fn receive_create_comment(
|
|||
// Its much easier to scrape them from the comment body, since the API has to do that
|
||||
// anyway.
|
||||
let mentions = scrape_text_for_mentions(&comment.content);
|
||||
let recipient_ids =
|
||||
send_local_notifs(mentions, comment.clone(), &user, post, context.pool(), true).await?;
|
||||
let recipient_ids = send_local_notifs(
|
||||
mentions,
|
||||
comment.clone(),
|
||||
person,
|
||||
post,
|
||||
context.pool(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Refetch the view
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
|
@ -64,9 +71,9 @@ pub(crate) async fn receive_update_comment(
|
|||
) -> Result<(), LemmyError> {
|
||||
let note = NoteExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
|
||||
.context(location_info!())?;
|
||||
let user = get_actor_as_user(&update, context, request_counter).await?;
|
||||
let person = get_actor_as_person(&update, context, request_counter).await?;
|
||||
|
||||
let comment = Comment::from_apub(¬e, context, user.actor_id()?, request_counter).await?;
|
||||
let comment = Comment::from_apub(¬e, context, person.actor_id(), request_counter).await?;
|
||||
|
||||
let comment_id = comment.id;
|
||||
let post_id = comment.post_id;
|
||||
|
@ -74,7 +81,7 @@ pub(crate) async fn receive_update_comment(
|
|||
|
||||
let mentions = scrape_text_for_mentions(&comment.content);
|
||||
let recipient_ids =
|
||||
send_local_notifs(mentions, comment, &user, post, context.pool(), false).await?;
|
||||
send_local_notifs(mentions, comment, person, post, context.pool(), false).await?;
|
||||
|
||||
// Refetch the view
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
|
@ -103,18 +110,18 @@ pub(crate) async fn receive_like_comment(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let user = get_actor_as_user(&like, context, request_counter).await?;
|
||||
let person = get_actor_as_person(&like, context, request_counter).await?;
|
||||
|
||||
let comment_id = comment.id;
|
||||
let like_form = CommentLikeForm {
|
||||
comment_id,
|
||||
post_id: comment.post_id,
|
||||
user_id: user.id,
|
||||
person_id: person.id,
|
||||
score: 1,
|
||||
};
|
||||
let user_id = user.id;
|
||||
let person_id = person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommentLike::remove(conn, user_id, comment_id)?;
|
||||
CommentLike::remove(conn, person_id, comment_id)?;
|
||||
CommentLike::like(conn, &like_form)
|
||||
})
|
||||
.await??;
|
||||
|
@ -148,18 +155,18 @@ pub(crate) async fn receive_dislike_comment(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let user = get_actor_as_user(&dislike, context, request_counter).await?;
|
||||
let person = get_actor_as_person(&dislike, context, request_counter).await?;
|
||||
|
||||
let comment_id = comment.id;
|
||||
let like_form = CommentLikeForm {
|
||||
comment_id,
|
||||
post_id: comment.post_id,
|
||||
user_id: user.id,
|
||||
person_id: person.id,
|
||||
score: -1,
|
||||
};
|
||||
let user_id = user.id;
|
||||
let person_id = person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommentLike::remove(conn, user_id, comment_id)?;
|
||||
CommentLike::remove(conn, person_id, comment_id)?;
|
||||
CommentLike::like(conn, &like_form)
|
||||
})
|
||||
.await??;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::activities::receive::get_actor_as_user;
|
||||
use crate::activities::receive::get_actor_as_person;
|
||||
use activitystreams::activity::{Dislike, Like};
|
||||
use lemmy_api_structs::{blocking, comment::CommentResponse};
|
||||
use lemmy_db_queries::{source::comment::Comment_, Likeable};
|
||||
use lemmy_db_schema::source::comment::{Comment, CommentLike};
|
||||
use lemmy_db_views::comment_view::CommentView;
|
||||
use lemmy_structs::{blocking, comment::CommentResponse};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
|
||||
|
||||
|
@ -13,12 +13,12 @@ pub(crate) async fn receive_undo_like_comment(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let user = get_actor_as_user(like, context, request_counter).await?;
|
||||
let person = get_actor_as_person(like, context, request_counter).await?;
|
||||
|
||||
let comment_id = comment.id;
|
||||
let user_id = user.id;
|
||||
let person_id = person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommentLike::remove(conn, user_id, comment_id)
|
||||
CommentLike::remove(conn, person_id, comment_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -51,12 +51,12 @@ pub(crate) async fn receive_undo_dislike_comment(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let user = get_actor_as_user(dislike, context, request_counter).await?;
|
||||
let person = get_actor_as_person(dislike, context, request_counter).await?;
|
||||
|
||||
let comment_id = comment.id;
|
||||
let user_id = user.id;
|
||||
let person_id = person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommentLike::remove(conn, user_id, comment_id)
|
||||
CommentLike::remove(conn, person_id, comment_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
|
|
@ -4,10 +4,10 @@ use activitystreams::{
|
|||
base::{AnyBase, ExtendsExt},
|
||||
};
|
||||
use anyhow::Context;
|
||||
use lemmy_api_structs::{blocking, community::CommunityResponse};
|
||||
use lemmy_db_queries::{source::community::Community_, ApubObject};
|
||||
use lemmy_db_schema::source::community::Community;
|
||||
use lemmy_db_views_actor::community_view::CommunityView;
|
||||
use lemmy_structs::{blocking, community::CommunityResponse};
|
||||
use lemmy_utils::{location_info, LemmyError};
|
||||
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
|
||||
use url::Url;
|
||||
|
@ -55,7 +55,7 @@ pub(crate) async fn receive_remove_community(
|
|||
.single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read_from_apub_id(conn, community_uri.as_str())
|
||||
Community::read_from_apub_id(conn, &community_uri.into())
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -137,7 +137,7 @@ pub(crate) async fn receive_undo_remove_community(
|
|||
.single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read_from_apub_id(conn, community_uri.as_str())
|
||||
Community::read_from_apub_id(conn, &community_uri.into())
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use crate::fetcher::user::get_or_fetch_and_upsert_user;
|
||||
use crate::fetcher::person::get_or_fetch_and_upsert_person;
|
||||
use activitystreams::{
|
||||
activity::{ActorAndObjectRef, ActorAndObjectRefExt},
|
||||
base::{AsBase, BaseExt},
|
||||
error::DomainError,
|
||||
};
|
||||
use anyhow::{anyhow, Context};
|
||||
use lemmy_db_schema::source::user::User_;
|
||||
use lemmy_db_schema::source::person::Person;
|
||||
use lemmy_utils::{location_info, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::debug;
|
||||
|
@ -28,18 +28,18 @@ where
|
|||
Err(anyhow!("Activity not supported").into())
|
||||
}
|
||||
|
||||
/// Reads the actor field of an activity and returns the corresponding `User_`.
|
||||
pub(crate) async fn get_actor_as_user<T, A>(
|
||||
/// Reads the actor field of an activity and returns the corresponding `Person`.
|
||||
pub(crate) async fn get_actor_as_person<T, A>(
|
||||
activity: &T,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<User_, LemmyError>
|
||||
) -> Result<Person, LemmyError>
|
||||
where
|
||||
T: AsBase<A> + ActorAndObjectRef,
|
||||
{
|
||||
let actor = activity.actor()?;
|
||||
let user_uri = actor.as_single_xsd_any_uri().context(location_info!())?;
|
||||
get_or_fetch_and_upsert_user(&user_uri, context, request_counter).await
|
||||
let person_uri = actor.as_single_xsd_any_uri().context(location_info!())?;
|
||||
get_or_fetch_and_upsert_person(&person_uri, context, request_counter).await
|
||||
}
|
||||
|
||||
/// Ensure that the ID of an incoming activity comes from the same domain as the actor. Optionally
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, PageExt};
|
||||
use crate::{activities::receive::get_actor_as_person, objects::FromApub, ActorType, PageExt};
|
||||
use activitystreams::{
|
||||
activity::{Create, Dislike, Like, Remove, Update},
|
||||
prelude::*,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use lemmy_api_structs::{blocking, post::PostResponse};
|
||||
use lemmy_db_queries::{source::post::Post_, Likeable};
|
||||
use lemmy_db_schema::source::post::{Post, PostLike, PostLikeForm};
|
||||
use lemmy_db_views::post_view::PostView;
|
||||
use lemmy_structs::{blocking, post::PostResponse};
|
||||
use lemmy_utils::{location_info, LemmyError};
|
||||
use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
|
||||
|
||||
|
@ -16,11 +16,11 @@ pub(crate) async fn receive_create_post(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let user = get_actor_as_user(&create, context, request_counter).await?;
|
||||
let person = get_actor_as_person(&create, context, request_counter).await?;
|
||||
let page = PageExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
|
||||
.context(location_info!())?;
|
||||
|
||||
let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?;
|
||||
let post = Post::from_apub(&page, context, person.actor_id(), request_counter).await?;
|
||||
|
||||
// Refetch the view
|
||||
let post_id = post.id;
|
||||
|
@ -45,11 +45,11 @@ pub(crate) async fn receive_update_post(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let user = get_actor_as_user(&update, context, request_counter).await?;
|
||||
let person = get_actor_as_person(&update, context, request_counter).await?;
|
||||
let page = PageExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
|
||||
.context(location_info!())?;
|
||||
|
||||
let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?;
|
||||
let post = Post::from_apub(&page, context, person.actor_id(), request_counter).await?;
|
||||
|
||||
let post_id = post.id;
|
||||
// Refetch the view
|
||||
|
@ -75,17 +75,17 @@ pub(crate) async fn receive_like_post(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let user = get_actor_as_user(&like, context, request_counter).await?;
|
||||
let person = get_actor_as_person(&like, context, request_counter).await?;
|
||||
|
||||
let post_id = post.id;
|
||||
let like_form = PostLikeForm {
|
||||
post_id,
|
||||
user_id: user.id,
|
||||
person_id: person.id,
|
||||
score: 1,
|
||||
};
|
||||
let user_id = user.id;
|
||||
let person_id = person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
PostLike::remove(conn, user_id, post_id)?;
|
||||
PostLike::remove(conn, person_id, post_id)?;
|
||||
PostLike::like(conn, &like_form)
|
||||
})
|
||||
.await??;
|
||||
|
@ -113,17 +113,17 @@ pub(crate) async fn receive_dislike_post(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let user = get_actor_as_user(&dislike, context, request_counter).await?;
|
||||
let person = get_actor_as_person(&dislike, context, request_counter).await?;
|
||||
|
||||
let post_id = post.id;
|
||||
let like_form = PostLikeForm {
|
||||
post_id,
|
||||
user_id: user.id,
|
||||
person_id: person.id,
|
||||
score: -1,
|
||||
};
|
||||
let user_id = user.id;
|
||||
let person_id = person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
PostLike::remove(conn, user_id, post_id)?;
|
||||
PostLike::remove(conn, person_id, post_id)?;
|
||||
PostLike::like(conn, &like_form)
|
||||
})
|
||||
.await??;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::activities::receive::get_actor_as_user;
|
||||
use crate::activities::receive::get_actor_as_person;
|
||||
use activitystreams::activity::{Dislike, Like};
|
||||
use lemmy_api_structs::{blocking, post::PostResponse};
|
||||
use lemmy_db_queries::{source::post::Post_, Likeable};
|
||||
use lemmy_db_schema::source::post::{Post, PostLike};
|
||||
use lemmy_db_views::post_view::PostView;
|
||||
use lemmy_structs::{blocking, post::PostResponse};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
|
||||
|
||||
|
@ -13,12 +13,12 @@ pub(crate) async fn receive_undo_like_post(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let user = get_actor_as_user(like, context, request_counter).await?;
|
||||
let person = get_actor_as_person(like, context, request_counter).await?;
|
||||
|
||||
let post_id = post.id;
|
||||
let user_id = user.id;
|
||||
let person_id = person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
PostLike::remove(conn, user_id, post_id)
|
||||
PostLike::remove(conn, person_id, post_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -45,12 +45,12 @@ pub(crate) async fn receive_undo_dislike_post(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let user = get_actor_as_user(dislike, context, request_counter).await?;
|
||||
let person = get_actor_as_person(dislike, context, request_counter).await?;
|
||||
|
||||
let post_id = post.id;
|
||||
let user_id = user.id;
|
||||
let person_id = person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
PostLike::remove(conn, user_id, post_id)
|
||||
PostLike::remove(conn, person_id, post_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
activities::receive::verify_activity_domains_valid,
|
||||
check_is_apub_id_valid,
|
||||
fetcher::user::get_or_fetch_and_upsert_user,
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
inbox::get_activity_to_and_cc,
|
||||
objects::FromApub,
|
||||
NoteExt,
|
||||
|
@ -13,10 +13,10 @@ use activitystreams::{
|
|||
public,
|
||||
};
|
||||
use anyhow::{anyhow, Context};
|
||||
use lemmy_api_structs::{blocking, person::PrivateMessageResponse};
|
||||
use lemmy_db_queries::source::private_message::PrivateMessage_;
|
||||
use lemmy_db_schema::source::private_message::PrivateMessage;
|
||||
use lemmy_db_views::private_message_view::PrivateMessageView;
|
||||
use lemmy_structs::{blocking, user::PrivateMessageResponse};
|
||||
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
||||
use lemmy_utils::{location_info, LemmyError};
|
||||
use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
|
||||
use url::Url;
|
||||
|
@ -50,12 +50,19 @@ pub(crate) async fn receive_create_private_message(
|
|||
private_message_view: message,
|
||||
};
|
||||
|
||||
// Send notifications to the local recipient, if one exists
|
||||
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 {
|
||||
op: UserOperation::CreatePrivateMessage,
|
||||
response: res,
|
||||
recipient_id,
|
||||
local_recipient_id,
|
||||
websocket_id: None,
|
||||
});
|
||||
|
||||
|
@ -91,11 +98,17 @@ pub(crate) async fn receive_update_private_message(
|
|||
};
|
||||
|
||||
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 {
|
||||
op: UserOperation::EditPrivateMessage,
|
||||
response: res,
|
||||
recipient_id,
|
||||
local_recipient_id,
|
||||
websocket_id: None,
|
||||
});
|
||||
|
||||
|
@ -123,11 +136,19 @@ pub(crate) async fn receive_delete_private_message(
|
|||
let res = PrivateMessageResponse {
|
||||
private_message_view: message,
|
||||
};
|
||||
|
||||
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 {
|
||||
op: UserOperation::EditPrivateMessage,
|
||||
response: res,
|
||||
recipient_id,
|
||||
local_recipient_id,
|
||||
websocket_id: None,
|
||||
});
|
||||
|
||||
|
@ -160,11 +181,19 @@ pub(crate) async fn receive_undo_delete_private_message(
|
|||
let res = PrivateMessageResponse {
|
||||
private_message_view: message,
|
||||
};
|
||||
|
||||
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 {
|
||||
op: UserOperation::EditPrivateMessage,
|
||||
response: res,
|
||||
recipient_id,
|
||||
local_recipient_id,
|
||||
websocket_id: None,
|
||||
});
|
||||
|
||||
|
@ -181,19 +210,19 @@ where
|
|||
{
|
||||
let to_and_cc = get_activity_to_and_cc(activity);
|
||||
if to_and_cc.len() != 1 {
|
||||
return Err(anyhow!("Private message can only be addressed to one user").into());
|
||||
return Err(anyhow!("Private message can only be addressed to one person").into());
|
||||
}
|
||||
if to_and_cc.contains(&public()) {
|
||||
return Err(anyhow!("Private message cant be public").into());
|
||||
}
|
||||
let user_id = activity
|
||||
let person_id = activity
|
||||
.actor()?
|
||||
.to_owned()
|
||||
.single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
check_is_apub_id_valid(&user_id)?;
|
||||
// check that the sender is a user, not a community
|
||||
get_or_fetch_and_upsert_user(&user_id, &context, request_counter).await?;
|
||||
check_is_apub_id_valid(&person_id)?;
|
||||
// check that the sender is a person, not a community
|
||||
get_or_fetch_and_upsert_person(&person_id, &context, request_counter).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
activities::send::generate_activity_id,
|
||||
activity_queue::{send_comment_mentions, send_to_community},
|
||||
extensions::context::lemmy_context,
|
||||
fetcher::user::get_or_fetch_and_upsert_user,
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
objects::ToApub,
|
||||
ActorType,
|
||||
ApubLikeableType,
|
||||
|
@ -26,12 +26,12 @@ use activitystreams::{
|
|||
};
|
||||
use anyhow::anyhow;
|
||||
use itertools::Itertools;
|
||||
use lemmy_api_structs::{blocking, WebFingerResponse};
|
||||
use lemmy_db_queries::{Crud, DbPool};
|
||||
use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post, user::User_};
|
||||
use lemmy_structs::{blocking, WebFingerResponse};
|
||||
use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
|
||||
use lemmy_utils::{
|
||||
request::{retry, RecvError},
|
||||
settings::Settings,
|
||||
settings::structs::Settings,
|
||||
utils::{scrape_text_for_mentions, MentionData},
|
||||
LemmyError,
|
||||
};
|
||||
|
@ -44,8 +44,8 @@ use url::Url;
|
|||
#[async_trait::async_trait(?Send)]
|
||||
impl ApubObjectType for Comment {
|
||||
/// Send out information about a newly created comment, to the followers of the community and
|
||||
/// mentioned users.
|
||||
async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
/// mentioned persons.
|
||||
async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let note = self.to_apub(context.pool()).await?;
|
||||
|
||||
let post_id = self.post_id;
|
||||
|
@ -59,7 +59,10 @@ impl ApubObjectType for Comment {
|
|||
|
||||
let maa = collect_non_local_mentions(&self, &community, context).await?;
|
||||
|
||||
let mut create = Create::new(creator.actor_id.to_owned(), note.into_any_base()?);
|
||||
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)?)
|
||||
|
@ -74,8 +77,8 @@ impl ApubObjectType for Comment {
|
|||
}
|
||||
|
||||
/// Send out information about an edited post, to the followers of the community and mentioned
|
||||
/// users.
|
||||
async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
/// persons.
|
||||
async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let note = self.to_apub(context.pool()).await?;
|
||||
|
||||
let post_id = self.post_id;
|
||||
|
@ -89,7 +92,10 @@ impl ApubObjectType for Comment {
|
|||
|
||||
let maa = collect_non_local_mentions(&self, &community, context).await?;
|
||||
|
||||
let mut update = Update::new(creator.actor_id.to_owned(), note.into_any_base()?);
|
||||
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)?)
|
||||
|
@ -103,7 +109,7 @@ impl ApubObjectType for Comment {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
|
@ -113,12 +119,15 @@ impl ApubObjectType for Comment {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
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(public())
|
||||
.set_many_ccs(vec![community.actor_id()?]);
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(delete, &creator, &community, context).await?;
|
||||
Ok(())
|
||||
|
@ -126,7 +135,7 @@ impl ApubObjectType for Comment {
|
|||
|
||||
async fn send_undo_delete(
|
||||
&self,
|
||||
creator: &User_,
|
||||
creator: &Person,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let post_id = self.post_id;
|
||||
|
@ -139,26 +148,32 @@ impl ApubObjectType for Comment {
|
|||
.await??;
|
||||
|
||||
// Generate a fake delete activity, with the correct object
|
||||
let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
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(public())
|
||||
.set_many_ccs(vec![community.actor_id()?]);
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
// Undo that fake activity
|
||||
let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
|
||||
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(public())
|
||||
.set_many_ccs(vec![community.actor_id()?]);
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(undo, &creator, &community, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_remove(&self, mod_: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
|
@ -168,18 +183,25 @@ impl ApubObjectType for Comment {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let mut remove = Remove::new(mod_.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
let mut remove = Remove::new(
|
||||
mod_.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
remove
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_undo_remove(
|
||||
&self,
|
||||
mod_: &Person,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
|
@ -190,20 +212,26 @@ impl ApubObjectType for Comment {
|
|||
.await??;
|
||||
|
||||
// Generate a fake delete activity, with the correct object
|
||||
let mut remove = Remove::new(mod_.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
let mut remove = Remove::new(
|
||||
mod_.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
remove
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()?]);
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
// Undo that fake activity
|
||||
let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
|
||||
let mut undo = Undo::new(
|
||||
mod_.actor_id.to_owned().into_inner(),
|
||||
remove.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
|
@ -212,7 +240,7 @@ impl ApubObjectType for Comment {
|
|||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ApubLikeableType for Comment {
|
||||
async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
|
@ -222,18 +250,21 @@ impl ApubLikeableType for Comment {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
let mut like = Like::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
like
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(LikeType::Like)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
|
@ -243,12 +274,15 @@ impl ApubLikeableType for Comment {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
let mut dislike = Dislike::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
dislike
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(DislikeType::Dislike)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
|
@ -256,7 +290,7 @@ impl ApubLikeableType for Comment {
|
|||
|
||||
async fn send_undo_like(
|
||||
&self,
|
||||
creator: &User_,
|
||||
creator: &Person,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let post_id = self.post_id;
|
||||
|
@ -268,20 +302,26 @@ impl ApubLikeableType for Comment {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
let mut like = Like::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
like
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(DislikeType::Dislike)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()?]);
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
// Undo that fake activity
|
||||
let mut undo = Undo::new(creator.actor_id.to_owned(), like.into_any_base()?);
|
||||
let mut undo = Undo::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
like.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
|
@ -306,21 +346,21 @@ impl MentionsAndAddresses {
|
|||
|
||||
/// This takes a comment, and builds a list of to_addresses, inboxes,
|
||||
/// and mention tags, so they know where to be sent to.
|
||||
/// Addresses are the users / addresses that go in the cc field.
|
||||
/// Addresses are the persons / addresses that go in the cc field.
|
||||
async fn collect_non_local_mentions(
|
||||
comment: &Comment,
|
||||
community: &Community,
|
||||
context: &LemmyContext,
|
||||
) -> Result<MentionsAndAddresses, LemmyError> {
|
||||
let parent_creator = get_comment_parent_creator(context.pool(), comment).await?;
|
||||
let mut addressed_ccs = vec![community.actor_id()?, parent_creator.actor_id()?];
|
||||
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_url()?];
|
||||
let mut inboxes = vec![parent_creator.get_shared_inbox_or_inbox_url()];
|
||||
|
||||
// Add the mention tag
|
||||
let mut tags = Vec::new();
|
||||
|
||||
// Get the user IDs for any mentions
|
||||
// Get the person IDs for any mentions
|
||||
let mentions = scrape_text_for_mentions(&comment.content)
|
||||
.into_iter()
|
||||
// Filter only the non-local ones
|
||||
|
@ -333,8 +373,8 @@ async fn collect_non_local_mentions(
|
|||
debug!("mention actor_id: {}", actor_id);
|
||||
addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
|
||||
|
||||
let mention_user = get_or_fetch_and_upsert_user(&actor_id, context, &mut 0).await?;
|
||||
inboxes.push(mention_user.get_shared_inbox_url()?);
|
||||
let mention_person = get_or_fetch_and_upsert_person(&actor_id, context, &mut 0).await?;
|
||||
inboxes.push(mention_person.get_shared_inbox_or_inbox_url());
|
||||
|
||||
let mut mention_tag = Mention::new();
|
||||
mention_tag.set_href(actor_id).set_name(mention.full_name());
|
||||
|
@ -351,9 +391,12 @@ async fn collect_non_local_mentions(
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns the apub ID of the user this comment is responding to. Meaning, in case this is a
|
||||
/// Returns the apub ID of the person 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.
|
||||
async fn get_comment_parent_creator(pool: &DbPool, comment: &Comment) -> Result<User_, LemmyError> {
|
||||
async fn get_comment_parent_creator(
|
||||
pool: &DbPool,
|
||||
comment: &Comment,
|
||||
) -> Result<Person, LemmyError> {
|
||||
let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
|
||||
let parent_comment =
|
||||
blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
|
||||
|
@ -363,10 +406,10 @@ async fn get_comment_parent_creator(pool: &DbPool, comment: &Comment) -> Result<
|
|||
let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
|
||||
parent_post.creator_id
|
||||
};
|
||||
Ok(blocking(pool, move |conn| User_::read(conn, parent_creator_id)).await??)
|
||||
Ok(blocking(pool, move |conn| Person::read(conn, parent_creator_id)).await??)
|
||||
}
|
||||
|
||||
/// Turns a user id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
|
||||
/// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
|
||||
/// using webfinger.
|
||||
async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
|
||||
let fetch_url = format!(
|
||||
|
@ -393,7 +436,5 @@ async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<U
|
|||
link
|
||||
.href
|
||||
.to_owned()
|
||||
.map(|u| Url::parse(&u))
|
||||
.transpose()?
|
||||
.ok_or_else(|| anyhow!("No href found.").into())
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ use crate::{
|
|||
activity_queue::{send_activity_single_dest, send_to_community_followers},
|
||||
check_is_apub_id_valid,
|
||||
extensions::context::lemmy_context,
|
||||
fetcher::user::get_or_fetch_and_upsert_user,
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
insert_activity,
|
||||
ActorType,
|
||||
};
|
||||
use activitystreams::{
|
||||
|
@ -23,20 +24,22 @@ use activitystreams::{
|
|||
};
|
||||
use anyhow::Context;
|
||||
use itertools::Itertools;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::DbPool;
|
||||
use lemmy_db_schema::source::community::Community;
|
||||
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{location_info, settings::Settings, LemmyError};
|
||||
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use url::Url;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActorType for Community {
|
||||
fn actor_id_str(&self) -> String {
|
||||
self.actor_id.to_owned()
|
||||
fn is_local(&self) -> bool {
|
||||
self.local
|
||||
}
|
||||
fn actor_id(&self) -> Url {
|
||||
self.actor_id.to_owned().into_inner()
|
||||
}
|
||||
|
||||
fn public_key(&self) -> Option<String> {
|
||||
self.public_key.to_owned()
|
||||
}
|
||||
|
@ -44,6 +47,14 @@ impl ActorType for Community {
|
|||
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(
|
||||
&self,
|
||||
_follow_actor_id: &Url,
|
||||
|
@ -60,7 +71,7 @@ impl ActorType for Community {
|
|||
unimplemented!()
|
||||
}
|
||||
|
||||
/// As a local community, accept the follow request from a remote user.
|
||||
/// As a local community, accept the follow request from a remote person.
|
||||
async fn send_accept_follow(
|
||||
&self,
|
||||
follow: Follow,
|
||||
|
@ -70,26 +81,29 @@ impl ActorType for Community {
|
|||
.actor()?
|
||||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
let user = get_or_fetch_and_upsert_user(actor_uri, context, &mut 0).await?;
|
||||
let person = get_or_fetch_and_upsert_person(actor_uri, context, &mut 0).await?;
|
||||
|
||||
let mut accept = Accept::new(self.actor_id.to_owned(), follow.into_any_base()?);
|
||||
let mut accept = Accept::new(
|
||||
self.actor_id.to_owned().into_inner(),
|
||||
follow.into_any_base()?,
|
||||
);
|
||||
accept
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(AcceptType::Accept)?)
|
||||
.set_to(user.actor_id()?);
|
||||
.set_to(person.actor_id());
|
||||
|
||||
send_activity_single_dest(accept, self, user.get_inbox_url()?, context).await?;
|
||||
send_activity_single_dest(accept, self, person.inbox_url.into(), context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If the creator of a community deletes the community, send this to all followers.
|
||||
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
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
|
||||
|
||||
send_to_community_followers(delete, self, context).await?;
|
||||
Ok(())
|
||||
|
@ -97,19 +111,19 @@ impl ActorType for Community {
|
|||
|
||||
/// 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> {
|
||||
let mut delete = Delete::new(self.actor_id()?, self.actor_id()?);
|
||||
let mut delete = Delete::new(self.actor_id(), self.actor_id());
|
||||
delete
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
|
||||
|
||||
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
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
|
||||
|
||||
send_to_community_followers(undo, self, context).await?;
|
||||
Ok(())
|
||||
|
@ -117,12 +131,12 @@ impl ActorType for Community {
|
|||
|
||||
/// If an admin removes a community, send this to all followers.
|
||||
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
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
|
||||
|
||||
send_to_community_followers(remove, self, context).await?;
|
||||
Ok(())
|
||||
|
@ -130,20 +144,20 @@ impl ActorType for Community {
|
|||
|
||||
/// If an admin reverts the removal of a community, send this to all followers.
|
||||
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
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
|
||||
|
||||
// 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
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(LikeType::Like)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
|
||||
|
||||
send_to_community_followers(undo, self, context).await?;
|
||||
Ok(())
|
||||
|
@ -151,17 +165,26 @@ impl ActorType for Community {
|
|||
|
||||
/// Wraps an activity sent to the community in an announce, and then sends the announce to all
|
||||
/// 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(
|
||||
&self,
|
||||
activity: AnyBase,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let mut announce = Announce::new(self.actor_id.to_owned(), activity);
|
||||
let inner_id = activity.id().context(location_info!())?;
|
||||
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
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(AnnounceType::Announce)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.get_followers_url()?]);
|
||||
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
|
||||
|
||||
send_to_community_followers(announce, self, context).await?;
|
||||
|
||||
|
@ -169,38 +192,21 @@ impl ActorType for Community {
|
|||
}
|
||||
|
||||
/// 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> {
|
||||
let id = self.id;
|
||||
|
||||
let inboxes = blocking(pool, move |conn| {
|
||||
let follows = blocking(pool, move |conn| {
|
||||
CommunityFollowerView::for_community(conn, id)
|
||||
})
|
||||
.await??;
|
||||
let inboxes = inboxes
|
||||
let inboxes = follows
|
||||
.into_iter()
|
||||
.filter(|i| !i.follower.local)
|
||||
.map(|u| -> Result<Url, LemmyError> {
|
||||
let url = Url::parse(&u.follower.actor_id)?;
|
||||
let domain = url.domain().context(location_info!())?;
|
||||
let port = if let Some(port) = url.port() {
|
||||
format!(":{}", port)
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
Ok(Url::parse(&format!(
|
||||
"{}://{}{}/inbox",
|
||||
Settings::get().get_protocol_string(),
|
||||
domain,
|
||||
port,
|
||||
))?)
|
||||
})
|
||||
.filter_map(Result::ok)
|
||||
.filter(|f| !f.follower.local)
|
||||
.map(|f| f.follower.shared_inbox_url.unwrap_or(f.follower.inbox_url))
|
||||
.map(|i| i.into_inner())
|
||||
.unique()
|
||||
// Don't send to blocked instances
|
||||
.filter(|inbox| check_is_apub_id_valid(inbox).is_ok())
|
||||
.unique()
|
||||
.collect();
|
||||
|
||||
Ok(inboxes)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use lemmy_utils::settings::Settings;
|
||||
use lemmy_utils::settings::structs::Settings;
|
||||
use url::{ParseError, Url};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) mod comment;
|
||||
pub(crate) mod community;
|
||||
pub(crate) mod person;
|
||||
pub(crate) mod post;
|
||||
pub(crate) mod private_message;
|
||||
pub(crate) mod user;
|
||||
|
||||
/// Generate a unique ID for an activity, in the format:
|
||||
/// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36`
|
||||
|
|
|
@ -13,20 +13,23 @@ use activitystreams::{
|
|||
base::{AnyBase, BaseExt, ExtendsExt},
|
||||
object::ObjectExt,
|
||||
};
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{ApubObject, DbPool, Followable};
|
||||
use lemmy_db_schema::source::{
|
||||
community::{Community, CommunityFollower, CommunityFollowerForm},
|
||||
user::User_,
|
||||
person::Person,
|
||||
};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use url::Url;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActorType for User_ {
|
||||
fn actor_id_str(&self) -> String {
|
||||
self.actor_id.to_owned()
|
||||
impl ActorType for Person {
|
||||
fn is_local(&self) -> bool {
|
||||
self.local
|
||||
}
|
||||
fn actor_id(&self) -> Url {
|
||||
self.actor_id.to_owned().into_inner()
|
||||
}
|
||||
|
||||
fn public_key(&self) -> Option<String> {
|
||||
|
@ -37,21 +40,29 @@ impl ActorType for User_ {
|
|||
self.private_key.to_owned()
|
||||
}
|
||||
|
||||
/// As a given local user, send out a follow request to a remote community.
|
||||
fn get_shared_inbox_or_inbox_url(&self) -> Url {
|
||||
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(
|
||||
&self,
|
||||
follow_actor_id: &Url,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let follow_actor_id = follow_actor_id.to_string();
|
||||
let follow_actor_id = follow_actor_id.to_owned();
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read_from_apub_id(conn, &follow_actor_id)
|
||||
Community::read_from_apub_id(conn, &follow_actor_id.into())
|
||||
})
|
||||
.await??;
|
||||
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: community.id,
|
||||
user_id: self.id,
|
||||
person_id: self.id,
|
||||
pending: true,
|
||||
};
|
||||
blocking(&context.pool(), move |conn| {
|
||||
|
@ -59,13 +70,13 @@ impl ActorType for User_ {
|
|||
})
|
||||
.await?;
|
||||
|
||||
let mut follow = Follow::new(self.actor_id.to_owned(), community.actor_id()?);
|
||||
let mut follow = Follow::new(self.actor_id.to_owned().into_inner(), community.actor_id());
|
||||
follow
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(FollowType::Follow)?)
|
||||
.set_to(community.actor_id()?);
|
||||
.set_to(community.actor_id());
|
||||
|
||||
send_activity_single_dest(follow, self, community.get_inbox_url()?, context).await?;
|
||||
send_activity_single_dest(follow, self, community.inbox_url.into(), context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -74,26 +85,29 @@ impl ActorType for User_ {
|
|||
follow_actor_id: &Url,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let follow_actor_id = follow_actor_id.to_string();
|
||||
let follow_actor_id = follow_actor_id.to_owned();
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read_from_apub_id(conn, &follow_actor_id)
|
||||
Community::read_from_apub_id(conn, &follow_actor_id.into())
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut follow = Follow::new(self.actor_id.to_owned(), community.actor_id()?);
|
||||
let mut follow = Follow::new(self.actor_id.to_owned().into_inner(), community.actor_id());
|
||||
follow
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(FollowType::Follow)?)
|
||||
.set_to(community.actor_id()?);
|
||||
.set_to(community.actor_id());
|
||||
|
||||
// Undo that fake activity
|
||||
let mut undo = Undo::new(Url::parse(&self.actor_id)?, follow.into_any_base()?);
|
||||
let mut undo = Undo::new(
|
||||
self.actor_id.to_owned().into_inner(),
|
||||
follow.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(community.actor_id()?);
|
||||
.set_to(community.actor_id());
|
||||
|
||||
send_activity_single_dest(undo, self, community.get_inbox_url()?, context).await?;
|
||||
send_activity_single_dest(undo, self, community.inbox_url.into(), context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -21,17 +21,16 @@ use activitystreams::{
|
|||
prelude::*,
|
||||
public,
|
||||
};
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::source::{community::Community, post::Post, user::User_};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_db_schema::source::{community::Community, person::Person, post::Post};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use url::Url;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ApubObjectType for Post {
|
||||
/// Send out information about a newly created post, to the followers of the community.
|
||||
async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let page = self.to_apub(context.pool()).await?;
|
||||
|
||||
let community_id = self.community_id;
|
||||
|
@ -40,19 +39,22 @@ impl ApubObjectType for Post {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let mut create = Create::new(creator.actor_id.to_owned(), page.into_any_base()?);
|
||||
let mut create = Create::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
page.into_any_base()?,
|
||||
);
|
||||
create
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(CreateType::Create)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send out information about an edited post, to the followers of the community.
|
||||
async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let page = self.to_apub(context.pool()).await?;
|
||||
|
||||
let community_id = self.community_id;
|
||||
|
@ -61,30 +63,36 @@ impl ApubObjectType for Post {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let mut update = Update::new(creator.actor_id.to_owned(), page.into_any_base()?);
|
||||
let mut update = Update::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
page.into_any_base()?,
|
||||
);
|
||||
update
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UpdateType::Update)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
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(public())
|
||||
.set_many_ccs(vec![community.actor_id()?]);
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(delete, creator, &community, context).await?;
|
||||
Ok(())
|
||||
|
@ -92,7 +100,7 @@ impl ApubObjectType for Post {
|
|||
|
||||
async fn send_undo_delete(
|
||||
&self,
|
||||
creator: &User_,
|
||||
creator: &Person,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community_id = self.community_id;
|
||||
|
@ -101,64 +109,83 @@ impl ApubObjectType for Post {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
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(public())
|
||||
.set_many_ccs(vec![community.actor_id()?]);
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
// Undo that fake activity
|
||||
let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
|
||||
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(public())
|
||||
.set_many_ccs(vec![community.actor_id()?]);
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(undo, creator, &community, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_remove(&self, mod_: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut remove = Remove::new(mod_.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
let mut remove = Remove::new(
|
||||
mod_.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
remove
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_undo_remove(
|
||||
&self,
|
||||
mod_: &Person,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut remove = Remove::new(mod_.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
let mut remove = Remove::new(
|
||||
mod_.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
remove
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()?]);
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
// Undo that fake activity
|
||||
let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
|
||||
let mut undo = Undo::new(
|
||||
mod_.actor_id.to_owned().into_inner(),
|
||||
remove.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
|
@ -167,37 +194,43 @@ impl ApubObjectType for Post {
|
|||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ApubLikeableType for Post {
|
||||
async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
let mut like = Like::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
like
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(LikeType::Like)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
let mut dislike = Dislike::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
dislike
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(DislikeType::Dislike)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
|
@ -205,7 +238,7 @@ impl ApubLikeableType for Post {
|
|||
|
||||
async fn send_undo_like(
|
||||
&self,
|
||||
creator: &User_,
|
||||
creator: &Person,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community_id = self.community_id;
|
||||
|
@ -214,20 +247,26 @@ impl ApubLikeableType for Post {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
let mut like = Like::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
like
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(LikeType::Like)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()?]);
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
// Undo that fake activity
|
||||
let mut undo = Undo::new(creator.actor_id.to_owned(), like.into_any_base()?);
|
||||
let mut undo = Undo::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
like.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.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?;
|
||||
Ok(())
|
||||
|
|
|
@ -16,96 +16,114 @@ use activitystreams::{
|
|||
},
|
||||
prelude::*,
|
||||
};
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::source::{private_message::PrivateMessage, user::User_};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage};
|
||||
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> {
|
||||
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| User_::read(conn, recipient_id)).await??;
|
||||
let recipient =
|
||||
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
|
||||
|
||||
let mut create = Create::new(creator.actor_id.to_owned(), note.into_any_base()?);
|
||||
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()?);
|
||||
.set_to(recipient.actor_id());
|
||||
|
||||
send_activity_single_dest(create, creator, recipient.get_inbox_url()?, context).await?;
|
||||
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: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
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| User_::read(conn, recipient_id)).await??;
|
||||
let recipient =
|
||||
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
|
||||
|
||||
let mut update = Update::new(creator.actor_id.to_owned(), note.into_any_base()?);
|
||||
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()?);
|
||||
.set_to(recipient.actor_id());
|
||||
|
||||
send_activity_single_dest(update, creator, recipient.get_inbox_url()?, context).await?;
|
||||
send_activity_single_dest(update, creator, recipient.inbox_url.into(), context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let recipient_id = self.recipient_id;
|
||||
let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
|
||||
let recipient =
|
||||
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
|
||||
|
||||
let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
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()?);
|
||||
.set_to(recipient.actor_id());
|
||||
|
||||
send_activity_single_dest(delete, creator, recipient.get_inbox_url()?, context).await?;
|
||||
send_activity_single_dest(delete, creator, recipient.inbox_url.into(), context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_undo_delete(
|
||||
&self,
|
||||
creator: &User_,
|
||||
creator: &Person,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let recipient_id = self.recipient_id;
|
||||
let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
|
||||
let recipient =
|
||||
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
|
||||
|
||||
let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
|
||||
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()?);
|
||||
.set_to(recipient.actor_id());
|
||||
|
||||
// Undo that fake activity
|
||||
let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
|
||||
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()?);
|
||||
.set_to(recipient.actor_id());
|
||||
|
||||
send_activity_single_dest(undo, creator, recipient.get_inbox_url()?, context).await?;
|
||||
send_activity_single_dest(undo, creator, recipient.inbox_url.into(), context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_remove(&self, _mod_: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
async fn send_remove(&self, _mod_: &Person, _context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn send_undo_remove(
|
||||
&self,
|
||||
_mod_: &User_,
|
||||
_mod_: &Person,
|
||||
_context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
unimplemented!()
|
||||
|
|
|
@ -21,13 +21,13 @@ use background_jobs::{
|
|||
};
|
||||
use itertools::Itertools;
|
||||
use lemmy_db_queries::DbPool;
|
||||
use lemmy_db_schema::source::{community::Community, user::User_};
|
||||
use lemmy_utils::{location_info, settings::Settings, LemmyError};
|
||||
use lemmy_db_schema::source::{community::Community, person::Person};
|
||||
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::{debug, warn};
|
||||
use reqwest::Client;
|
||||
use serde::{export::fmt::Debug, Deserialize, Serialize};
|
||||
use std::{collections::BTreeMap, env, future::Future, pin::Pin};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::BTreeMap, env, fmt::Debug, future::Future, pin::Pin};
|
||||
use url::Url;
|
||||
|
||||
/// Sends a local activity to a single, remote actor.
|
||||
|
@ -88,13 +88,13 @@ where
|
|||
.await?
|
||||
.iter()
|
||||
.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())
|
||||
.map(|inbox| inbox.to_owned())
|
||||
.collect();
|
||||
debug!(
|
||||
"Sending activity {:?} to followers of {}",
|
||||
&activity.id_unchecked(),
|
||||
&activity.id_unchecked().map(|i| i.to_string()),
|
||||
&community.actor_id
|
||||
);
|
||||
|
||||
|
@ -112,7 +112,7 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends an activity from a local user to a remote community.
|
||||
/// Sends an activity from a local person to a remote community.
|
||||
///
|
||||
/// * `activity` the activity to send
|
||||
/// * `creator` the creator of the activity
|
||||
|
@ -120,7 +120,7 @@ where
|
|||
///
|
||||
pub(crate) async fn send_to_community<T, Kind>(
|
||||
activity: T,
|
||||
creator: &User_,
|
||||
creator: &Person,
|
||||
community: &Community,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError>
|
||||
|
@ -135,7 +135,7 @@ where
|
|||
.send_announce(activity.into_any_base()?, context)
|
||||
.await?;
|
||||
} else {
|
||||
let inbox = community.get_shared_inbox_url()?;
|
||||
let inbox = community.get_shared_inbox_or_inbox_url();
|
||||
check_is_apub_id_valid(&inbox)?;
|
||||
debug!(
|
||||
"Sending activity {:?} to community {}",
|
||||
|
@ -157,13 +157,13 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends notification to any users mentioned in a comment
|
||||
/// Sends notification to any persons mentioned in a comment
|
||||
///
|
||||
/// * `creator` user who created the comment
|
||||
/// * `mentions` list of inboxes of users which are mentioned in the comment
|
||||
/// * `creator` person who created the comment
|
||||
/// * `mentions` list of inboxes of persons which are mentioned in the comment
|
||||
/// * `activity` either a `Create/Note` or `Update/Note`
|
||||
pub(crate) async fn send_comment_mentions<T, Kind>(
|
||||
creator: &User_,
|
||||
creator: &Person,
|
||||
mentions: Vec<Url>,
|
||||
activity: T,
|
||||
context: &LemmyContext,
|
||||
|
@ -215,7 +215,7 @@ where
|
|||
Kind: Serialize,
|
||||
<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(());
|
||||
}
|
||||
|
||||
|
@ -223,7 +223,7 @@ where
|
|||
let hostname = Settings::get().get_hostname_without_port()?;
|
||||
let inboxes: Vec<&Url> = inboxes
|
||||
.iter()
|
||||
.filter(|i| i.domain().unwrap() != hostname)
|
||||
.filter(|i| i.domain().expect("valid inbox url") != hostname)
|
||||
.collect();
|
||||
|
||||
let activity = activity.into_any_base()?;
|
||||
|
@ -240,7 +240,7 @@ where
|
|||
let message = SendActivityTask {
|
||||
activity: serialised_activity.to_owned(),
|
||||
inbox: i.to_owned(),
|
||||
actor_id: actor.actor_id()?,
|
||||
actor_id: actor.actor_id(),
|
||||
private_key: actor.private_key().context(location_info!())?,
|
||||
};
|
||||
if env::var("LEMMY_TEST_SEND_SYNC").is_ok() {
|
||||
|
|
|
@ -6,7 +6,6 @@ pub(crate) fn lemmy_context() -> Result<Vec<AnyBase>, LemmyError> {
|
|||
let context_ext = AnyBase::from_arbitrary_json(json!(
|
||||
{
|
||||
"sc": "http://schema.org#",
|
||||
"category": "sc:category",
|
||||
"sensitive": "as:sensitive",
|
||||
"stickied": "as:stickied",
|
||||
"comments_enabled": {
|
||||
|
|
|
@ -1,42 +1,20 @@
|
|||
use activitystreams::unparsed::UnparsedMutExt;
|
||||
use activitystreams_ext::UnparsedExtension;
|
||||
use diesel::PgConnection;
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::source::category::Category;
|
||||
use lemmy_utils::LemmyError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Activitystreams extension to allow (de)serializing additional Community fields `category` and
|
||||
/// Activitystreams extension to allow (de)serializing additional Community field
|
||||
/// `sensitive` (called 'nsfw' in Lemmy).
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GroupExtension {
|
||||
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,
|
||||
pub sensitive: Option<bool>,
|
||||
}
|
||||
|
||||
impl GroupExtension {
|
||||
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,
|
||||
};
|
||||
pub fn new(sensitive: bool) -> Result<GroupExtension, LemmyError> {
|
||||
Ok(GroupExtension {
|
||||
category: group_category,
|
||||
sensitive,
|
||||
sensitive: Some(sensitive),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -49,13 +27,11 @@ where
|
|||
|
||||
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
|
||||
Ok(GroupExtension {
|
||||
category: unparsed_mut.remove("category")?,
|
||||
sensitive: unparsed_mut.remove("sensitive")?,
|
||||
})
|
||||
}
|
||||
|
||||
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
|
||||
unparsed_mut.insert("category", self.category)?;
|
||||
unparsed_mut.insert("sensitive", self.sensitive)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ use serde::{Deserialize, Serialize};
|
|||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PageExtension {
|
||||
pub comments_enabled: bool,
|
||||
pub sensitive: bool,
|
||||
pub stickied: bool,
|
||||
pub comments_enabled: Option<bool>,
|
||||
pub sensitive: Option<bool>,
|
||||
pub stickied: Option<bool>,
|
||||
}
|
||||
|
||||
impl<U> UnparsedExtension<U> for PageExtension
|
||||
|
|
|
@ -95,20 +95,20 @@ pub(crate) fn verify_signature(
|
|||
}
|
||||
}
|
||||
|
||||
/// Extension for actor public key, which is needed on user and community for HTTP signatures.
|
||||
/// Extension for actor public key, which is needed on person and community for HTTP signatures.
|
||||
///
|
||||
/// Taken from: https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PublicKeyExtension {
|
||||
pub public_key: PublicKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PublicKey {
|
||||
pub id: String,
|
||||
pub owner: String,
|
||||
pub owner: Url,
|
||||
pub public_key_pem: String,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,24 @@
|
|||
use crate::{
|
||||
check_is_apub_id_valid,
|
||||
fetcher::{
|
||||
fetch::fetch_remote_object,
|
||||
get_or_fetch_and_upsert_user,
|
||||
get_or_fetch_and_upsert_person,
|
||||
is_deleted,
|
||||
should_refetch_actor,
|
||||
},
|
||||
inbox::person_inbox::receive_announce,
|
||||
objects::FromApub,
|
||||
ActorType,
|
||||
GroupExt,
|
||||
PageExt,
|
||||
};
|
||||
use activitystreams::{
|
||||
base::{BaseExt, ExtendsExt},
|
||||
actor::ApActorExt,
|
||||
collection::{CollectionExt, OrderedCollection},
|
||||
object::ObjectExt,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use diesel::result::Error::NotFound;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{source::community::Community_, ApubObject, Joinable};
|
||||
use lemmy_db_schema::source::{
|
||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||
post::Post,
|
||||
};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_db_schema::source::community::{Community, CommunityModerator, CommunityModeratorForm};
|
||||
use lemmy_utils::{location_info, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::debug;
|
||||
|
@ -40,7 +35,7 @@ pub(crate) async fn get_or_fetch_and_upsert_community(
|
|||
) -> Result<Community, LemmyError> {
|
||||
let apub_id_owned = apub_id.to_owned();
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read_from_apub_id(conn, apub_id_owned.as_str())
|
||||
Community::read_from_apub_id(conn, &apub_id_owned.into())
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
@ -97,7 +92,7 @@ async fn fetch_remote_community(
|
|||
let mut creator_and_moderators = Vec::new();
|
||||
|
||||
for uri in creator_and_moderator_uris {
|
||||
let c_or_m = get_or_fetch_and_upsert_user(uri, context, recursion_counter).await?;
|
||||
let c_or_m = get_or_fetch_and_upsert_person(uri, context, recursion_counter).await?;
|
||||
|
||||
creator_and_moderators.push(c_or_m);
|
||||
}
|
||||
|
@ -109,7 +104,7 @@ async fn fetch_remote_community(
|
|||
for mod_ in creator_and_moderators {
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
community_id,
|
||||
user_id: mod_.id,
|
||||
person_id: mod_.id,
|
||||
};
|
||||
|
||||
CommunityModerator::join(conn, &community_moderator_form)?;
|
||||
|
@ -119,29 +114,32 @@ async fn fetch_remote_community(
|
|||
.await??;
|
||||
}
|
||||
|
||||
// fetch outbox (maybe make this conditional)
|
||||
let outbox = fetch_remote_object::<OrderedCollection>(
|
||||
context.client(),
|
||||
&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
|
||||
// only fetch outbox for new communities, otherwise this can create an infinite loop
|
||||
if old_community.is_none() {
|
||||
let outbox = group.inner.outbox()?.context(location_info!())?;
|
||||
fetch_community_outbox(context, outbox, &community, recursion_counter).await?
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -10,8 +10,9 @@ use url::Url;
|
|||
/// Maximum number of HTTP requests allowed to handle a single incoming activity (or a single object
|
||||
/// fetch through the search).
|
||||
///
|
||||
/// Tests are passing with a value of 5, so 10 should be safe for production.
|
||||
static MAX_REQUEST_NUMBER: i32 = 10;
|
||||
/// A community fetch will load the outbox with up to 20 items, and fetch the creator for each item.
|
||||
/// So we are looking at a maximum of 22 requests (rounded up just to be safe).
|
||||
static MAX_REQUEST_NUMBER: i32 = 25;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(in crate::fetcher) struct FetchError {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
pub(crate) mod community;
|
||||
mod fetch;
|
||||
pub(crate) mod objects;
|
||||
pub(crate) mod person;
|
||||
pub mod search;
|
||||
pub(crate) mod user;
|
||||
|
||||
use crate::{
|
||||
fetcher::{
|
||||
community::get_or_fetch_and_upsert_community,
|
||||
fetch::FetchError,
|
||||
user::get_or_fetch_and_upsert_user,
|
||||
person::get_or_fetch_and_upsert_person,
|
||||
},
|
||||
ActorType,
|
||||
};
|
||||
|
@ -37,8 +37,8 @@ where
|
|||
false
|
||||
}
|
||||
|
||||
/// Get a remote actor from its apub ID (either a user or a community). Thin wrapper around
|
||||
/// `get_or_fetch_and_upsert_user()` and `get_or_fetch_and_upsert_community()`.
|
||||
/// Get a remote actor from its apub ID (either a person or a community). Thin wrapper around
|
||||
/// `get_or_fetch_and_upsert_person()` and `get_or_fetch_and_upsert_community()`.
|
||||
///
|
||||
/// 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.
|
||||
|
@ -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 actor: Box<dyn ActorType> = match community {
|
||||
Ok(c) => Box::new(c),
|
||||
Err(_) => Box::new(get_or_fetch_and_upsert_user(apub_id, context, recursion_counter).await?),
|
||||
Err(_) => Box::new(get_or_fetch_and_upsert_person(apub_id, context, recursion_counter).await?),
|
||||
};
|
||||
Ok(actor)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::{fetcher::fetch::fetch_remote_object, objects::FromApub, NoteExt, PageExt};
|
||||
use anyhow::anyhow;
|
||||
use diesel::result::Error::NotFound;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{ApubObject, Crud};
|
||||
use lemmy_db_schema::source::{comment::Comment, post::Post};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::debug;
|
||||
|
@ -20,7 +20,7 @@ pub(crate) async fn get_or_fetch_and_insert_post(
|
|||
) -> Result<Post, LemmyError> {
|
||||
let post_ap_id_owned = post_ap_id.to_owned();
|
||||
let post = blocking(context.pool(), move |conn| {
|
||||
Post::read_from_apub_id(conn, post_ap_id_owned.as_str())
|
||||
Post::read_from_apub_id(conn, &post_ap_id_owned.into())
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
@ -49,7 +49,7 @@ pub(crate) async fn get_or_fetch_and_insert_comment(
|
|||
) -> Result<Comment, LemmyError> {
|
||||
let comment_ap_id_owned = comment_ap_id.to_owned();
|
||||
let comment = blocking(context.pool(), move |conn| {
|
||||
Comment::read_from_apub_id(conn, comment_ap_id_owned.as_str())
|
||||
Comment::read_from_apub_id(conn, &comment_ap_id_owned.into())
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
|
|
@ -5,66 +5,68 @@ use crate::{
|
|||
};
|
||||
use anyhow::anyhow;
|
||||
use diesel::result::Error::NotFound;
|
||||
use lemmy_db_queries::{source::user::User, ApubObject};
|
||||
use lemmy_db_schema::source::user::User_;
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{source::person::Person_, ApubObject};
|
||||
use lemmy_db_schema::source::person::Person;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::debug;
|
||||
use url::Url;
|
||||
|
||||
/// Get a user from its apub ID.
|
||||
/// Get a person from its apub ID.
|
||||
///
|
||||
/// 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.
|
||||
pub(crate) async fn get_or_fetch_and_upsert_user(
|
||||
pub(crate) async fn get_or_fetch_and_upsert_person(
|
||||
apub_id: &Url,
|
||||
context: &LemmyContext,
|
||||
recursion_counter: &mut i32,
|
||||
) -> Result<User_, LemmyError> {
|
||||
) -> Result<Person, LemmyError> {
|
||||
let apub_id_owned = apub_id.to_owned();
|
||||
let user = blocking(context.pool(), move |conn| {
|
||||
User_::read_from_apub_id(conn, apub_id_owned.as_ref())
|
||||
let person = blocking(context.pool(), move |conn| {
|
||||
Person::read_from_apub_id(conn, &apub_id_owned.into())
|
||||
})
|
||||
.await?;
|
||||
|
||||
match user {
|
||||
match person {
|
||||
// If its older than a day, re-fetch it
|
||||
Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => {
|
||||
debug!("Fetching and updating from remote user: {}", apub_id);
|
||||
debug!("Fetching and updating from remote person: {}", apub_id);
|
||||
let person =
|
||||
fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await;
|
||||
|
||||
if is_deleted(&person) {
|
||||
// TODO: use User_::update_deleted() once implemented
|
||||
// TODO: use Person::update_deleted() once implemented
|
||||
blocking(context.pool(), move |conn| {
|
||||
User_::delete_account(conn, u.id)
|
||||
Person::delete_account(conn, u.id)
|
||||
})
|
||||
.await??;
|
||||
return Err(anyhow!("User was deleted by remote instance").into());
|
||||
return Err(anyhow!("Person was deleted by remote instance").into());
|
||||
} else if person.is_err() {
|
||||
return Ok(u);
|
||||
}
|
||||
|
||||
let user = User_::from_apub(&person?, context, apub_id.to_owned(), recursion_counter).await?;
|
||||
let person =
|
||||
Person::from_apub(&person?, context, apub_id.to_owned(), recursion_counter).await?;
|
||||
|
||||
let user_id = user.id;
|
||||
let person_id = person.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
User_::mark_as_updated(conn, user_id)
|
||||
Person::mark_as_updated(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(user)
|
||||
Ok(person)
|
||||
}
|
||||
Ok(u) => Ok(u),
|
||||
Err(NotFound {}) => {
|
||||
debug!("Fetching and creating remote user: {}", apub_id);
|
||||
debug!("Fetching and creating remote person: {}", apub_id);
|
||||
let person =
|
||||
fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await?;
|
||||
|
||||
let user = User_::from_apub(&person, context, apub_id.to_owned(), recursion_counter).await?;
|
||||
let person =
|
||||
Person::from_apub(&person, context, apub_id.to_owned(), recursion_counter).await?;
|
||||
|
||||
Ok(user)
|
||||
Ok(person)
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
fetcher::{
|
||||
fetch::fetch_remote_object,
|
||||
get_or_fetch_and_upsert_community,
|
||||
get_or_fetch_and_upsert_user,
|
||||
get_or_fetch_and_upsert_person,
|
||||
is_deleted,
|
||||
},
|
||||
find_object_by_id,
|
||||
|
@ -15,34 +15,34 @@ use crate::{
|
|||
};
|
||||
use activitystreams::base::BaseExt;
|
||||
use anyhow::{anyhow, Context};
|
||||
use lemmy_api_structs::{blocking, site::SearchResponse};
|
||||
use lemmy_db_queries::{
|
||||
source::{
|
||||
comment::Comment_,
|
||||
community::Community_,
|
||||
person::Person_,
|
||||
post::Post_,
|
||||
private_message::PrivateMessage_,
|
||||
user::User,
|
||||
},
|
||||
SearchType,
|
||||
};
|
||||
use lemmy_db_schema::source::{
|
||||
comment::Comment,
|
||||
community::Community,
|
||||
person::Person,
|
||||
post::Post,
|
||||
private_message::PrivateMessage,
|
||||
user::User_,
|
||||
};
|
||||
use lemmy_db_views::{comment_view::CommentView, post_view::PostView};
|
||||
use lemmy_db_views_actor::{community_view::CommunityView, user_view::UserViewSafe};
|
||||
use lemmy_structs::{blocking, site::SearchResponse};
|
||||
use lemmy_utils::{settings::Settings, LemmyError};
|
||||
use lemmy_db_views_actor::{community_view::CommunityView, person_view::PersonViewSafe};
|
||||
use lemmy_utils::{settings::structs::Settings, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::debug;
|
||||
use url::Url;
|
||||
|
||||
/// The types of ActivityPub objects that can be fetched directly by searching for their ID.
|
||||
#[serde(untagged)]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
enum SearchAcceptedObjects {
|
||||
Person(Box<PersonExt>),
|
||||
Group(Box<GroupExt>),
|
||||
|
@ -66,7 +66,7 @@ pub async fn search_by_apub_id(
|
|||
debug!("Search for {}", query);
|
||||
let split = query.split('@').collect::<Vec<&str>>();
|
||||
|
||||
// User type will look like ['', username, instance]
|
||||
// Person type will look like ['', username, instance]
|
||||
// Community will look like [!community, instance]
|
||||
let (name, instance) = if split.len() == 3 {
|
||||
(format!("/u/{}", split[1]), split[2])
|
||||
|
@ -122,13 +122,13 @@ async fn build_response(
|
|||
|
||||
match fetch_response {
|
||||
SearchAcceptedObjects::Person(p) => {
|
||||
let user_uri = p.inner.id(domain)?.context("person has no id")?;
|
||||
let person_uri = p.inner.id(domain)?.context("person has no id")?;
|
||||
|
||||
let user = get_or_fetch_and_upsert_user(&user_uri, context, recursion_counter).await?;
|
||||
let person = get_or_fetch_and_upsert_person(&person_uri, context, recursion_counter).await?;
|
||||
|
||||
response.users = vec![
|
||||
blocking(context.pool(), move |conn| {
|
||||
UserViewSafe::read(conn, user.id)
|
||||
PersonViewSafe::read(conn, person.id)
|
||||
})
|
||||
.await??,
|
||||
];
|
||||
|
@ -182,10 +182,10 @@ async fn delete_object_locally(query_url: &Url, context: &LemmyContext) -> Resul
|
|||
})
|
||||
.await??;
|
||||
}
|
||||
Object::User(u) => {
|
||||
Object::Person(u) => {
|
||||
// TODO: implement update_deleted() for user, move it to ApubObject trait
|
||||
blocking(context.pool(), move |conn| {
|
||||
User_::delete_account(conn, u.id)
|
||||
Person::delete_account(conn, u.id)
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ use crate::{
|
|||
};
|
||||
use actix_web::{body::Body, web, web::Path, HttpResponse};
|
||||
use diesel::result::Error::NotFound;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::source::comment::Comment;
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_db_schema::{source::comment::Comment, CommentId};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::Deserialize;
|
||||
|
@ -21,7 +21,7 @@ pub async fn get_apub_comment(
|
|||
info: Path<CommentQuery>,
|
||||
context: web::Data<LemmyContext>,
|
||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||
let id = info.comment_id.parse::<i32>()?;
|
||||
let id = CommentId(info.comment_id.parse::<i32>()?);
|
||||
let comment = blocking(context.pool(), move |conn| Comment::read(conn, id)).await??;
|
||||
if !comment.local {
|
||||
return Err(NotFound.into());
|
||||
|
|
|
@ -5,14 +5,14 @@ use crate::{
|
|||
ActorType,
|
||||
};
|
||||
use activitystreams::{
|
||||
base::{AnyBase, BaseExt, ExtendsExt},
|
||||
base::{AnyBase, BaseExt},
|
||||
collection::{CollectionExt, OrderedCollection, UnorderedCollection},
|
||||
};
|
||||
use actix_web::{body::Body, web, HttpResponse};
|
||||
use lemmy_db_queries::source::{community::Community_, post::Post_};
|
||||
use lemmy_db_schema::source::{community::Community, post::Post};
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::source::{activity::Activity_, community::Community_};
|
||||
use lemmy_db_schema::source::{activity::Activity, community::Community};
|
||||
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::Deserialize;
|
||||
|
@ -60,7 +60,7 @@ pub async fn get_apub_community_followers(
|
|||
let mut collection = UnorderedCollection::new();
|
||||
collection
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(community.get_followers_url()?)
|
||||
.set_id(community.followers_url.into())
|
||||
.set_total_items(community_followers.len() as u64);
|
||||
Ok(create_apub_response(&collection))
|
||||
}
|
||||
|
@ -76,21 +76,20 @@ pub async fn get_apub_community_outbox(
|
|||
})
|
||||
.await??;
|
||||
|
||||
let community_id = community.id;
|
||||
let posts = blocking(context.pool(), move |conn| {
|
||||
Post::list_for_community(conn, community_id)
|
||||
let community_actor_id = community.actor_id.to_owned();
|
||||
let activities = blocking(context.pool(), move |conn| {
|
||||
Activity::read_community_outbox(conn, &community_actor_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut pages: Vec<AnyBase> = vec![];
|
||||
for p in posts {
|
||||
pages.push(p.to_apub(context.pool()).await?.into_any_base()?);
|
||||
}
|
||||
|
||||
let len = pages.len();
|
||||
let activities = activities
|
||||
.iter()
|
||||
.map(AnyBase::from_arbitrary_json)
|
||||
.collect::<Result<Vec<AnyBase>, serde_json::Error>>()?;
|
||||
let len = activities.len();
|
||||
let mut collection = OrderedCollection::new();
|
||||
collection
|
||||
.set_many_items(pages)
|
||||
.set_many_items(activities)
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(community.get_outbox_url()?)
|
||||
.set_total_items(len as u64);
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
use crate::APUB_JSON_CONTENT_TYPE;
|
||||
use actix_web::{body::Body, web, HttpResponse};
|
||||
use http::StatusCode;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::source::activity::Activity_;
|
||||
use lemmy_db_schema::source::activity::Activity;
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{settings::Settings, LemmyError};
|
||||
use lemmy_utils::{settings::structs::Settings, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
pub mod comment;
|
||||
pub mod community;
|
||||
pub mod person;
|
||||
pub mod post;
|
||||
pub mod user;
|
||||
|
||||
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
|
||||
/// headers.
|
||||
|
@ -46,12 +47,13 @@ pub async fn get_activity(
|
|||
context: web::Data<LemmyContext>,
|
||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||
let settings = Settings::get();
|
||||
let activity_id = format!(
|
||||
let activity_id = Url::parse(&format!(
|
||||
"{}/activities/{}/{}",
|
||||
settings.get_protocol_and_hostname(),
|
||||
info.type_,
|
||||
info.id
|
||||
);
|
||||
))?
|
||||
.into();
|
||||
let activity = blocking(context.pool(), move |conn| {
|
||||
Activity::read_from_apub_id(&conn, &activity_id)
|
||||
})
|
||||
|
|
|
@ -9,70 +9,70 @@ use activitystreams::{
|
|||
collection::{CollectionExt, OrderedCollection},
|
||||
};
|
||||
use actix_web::{body::Body, web, HttpResponse};
|
||||
use lemmy_db_queries::source::user::User;
|
||||
use lemmy_db_schema::source::user::User_;
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::source::person::Person_;
|
||||
use lemmy_db_schema::source::person::Person;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UserQuery {
|
||||
pub struct PersonQuery {
|
||||
user_name: String,
|
||||
}
|
||||
|
||||
/// Return the ActivityPub json representation of a local user over HTTP.
|
||||
pub async fn get_apub_user_http(
|
||||
info: web::Path<UserQuery>,
|
||||
/// Return the ActivityPub json representation of a local person over HTTP.
|
||||
pub async fn get_apub_person_http(
|
||||
info: web::Path<PersonQuery>,
|
||||
context: web::Data<LemmyContext>,
|
||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||
let user_name = info.into_inner().user_name;
|
||||
// TODO: this needs to be able to read deleted users, so that it can send tombstones
|
||||
let user = blocking(context.pool(), move |conn| {
|
||||
User_::find_by_email_or_username(conn, &user_name)
|
||||
// TODO: this needs to be able to read deleted persons, so that it can send tombstones
|
||||
let person = blocking(context.pool(), move |conn| {
|
||||
Person::find_by_name(conn, &user_name)
|
||||
})
|
||||
.await??;
|
||||
|
||||
if !user.deleted {
|
||||
let apub = user.to_apub(context.pool()).await?;
|
||||
if !person.deleted {
|
||||
let apub = person.to_apub(context.pool()).await?;
|
||||
|
||||
Ok(create_apub_response(&apub))
|
||||
} else {
|
||||
Ok(create_apub_tombstone_response(&user.to_tombstone()?))
|
||||
Ok(create_apub_tombstone_response(&person.to_tombstone()?))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_apub_user_outbox(
|
||||
info: web::Path<UserQuery>,
|
||||
pub async fn get_apub_person_outbox(
|
||||
info: web::Path<PersonQuery>,
|
||||
context: web::Data<LemmyContext>,
|
||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||
let user = blocking(context.pool(), move |conn| {
|
||||
User_::read_from_name(&conn, &info.user_name)
|
||||
let person = blocking(context.pool(), move |conn| {
|
||||
Person::find_by_name(&conn, &info.user_name)
|
||||
})
|
||||
.await??;
|
||||
// TODO: populate the user outbox
|
||||
// TODO: populate the person outbox
|
||||
let mut collection = OrderedCollection::new();
|
||||
collection
|
||||
.set_many_items(Vec::<Url>::new())
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(user.get_outbox_url()?)
|
||||
.set_id(person.get_outbox_url()?)
|
||||
.set_total_items(0_u64);
|
||||
Ok(create_apub_response(&collection))
|
||||
}
|
||||
|
||||
pub async fn get_apub_user_inbox(
|
||||
info: web::Path<UserQuery>,
|
||||
pub async fn get_apub_person_inbox(
|
||||
info: web::Path<PersonQuery>,
|
||||
context: web::Data<LemmyContext>,
|
||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||
let user = blocking(context.pool(), move |conn| {
|
||||
User_::read_from_name(&conn, &info.user_name)
|
||||
let person = blocking(context.pool(), move |conn| {
|
||||
Person::find_by_name(&conn, &info.user_name)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut collection = OrderedCollection::new();
|
||||
collection
|
||||
.set_id(format!("{}/inbox", user.actor_id).parse()?)
|
||||
.set_id(format!("{}/inbox", person.actor_id.into_inner()).parse()?)
|
||||
.set_many_contexts(lemmy_context()?);
|
||||
Ok(create_apub_response(&collection))
|
||||
}
|
|
@ -4,9 +4,9 @@ use crate::{
|
|||
};
|
||||
use actix_web::{body::Body, web, HttpResponse};
|
||||
use diesel::result::Error::NotFound;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::source::post::Post;
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_db_schema::{source::post::Post, PostId};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::Deserialize;
|
||||
|
@ -21,7 +21,7 @@ pub async fn get_apub_post(
|
|||
info: web::Path<PostQuery>,
|
||||
context: web::Data<LemmyContext>,
|
||||
) -> Result<HttpResponse<Body>, LemmyError> {
|
||||
let id = info.post_id.parse::<i32>()?;
|
||||
let id = PostId(info.post_id.parse::<i32>()?);
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, id)).await??;
|
||||
if !post.local {
|
||||
return Err(NotFound.into());
|
||||
|
|
|
@ -26,13 +26,16 @@ use activitystreams::{
|
|||
};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use anyhow::{anyhow, Context};
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{source::community::Community_, ApubObject, DbPool, Followable};
|
||||
use lemmy_db_schema::source::{
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::{Community, CommunityFollower, CommunityFollowerForm},
|
||||
user::User_,
|
||||
person::Person,
|
||||
},
|
||||
CommunityId,
|
||||
};
|
||||
use lemmy_db_views_actor::community_user_ban_view::CommunityUserBanView;
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_db_views_actor::community_person_ban_view::CommunityPersonBanView;
|
||||
use lemmy_utils::{location_info, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::info;
|
||||
|
@ -44,8 +47,8 @@ use url::Url;
|
|||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub enum CommunityValidTypes {
|
||||
Follow, // follow request from a user
|
||||
Undo, // unfollow from a user
|
||||
Follow, // follow request from a person
|
||||
Undo, // unfollow from a person
|
||||
Create, // create post or comment
|
||||
Update, // update post or comment
|
||||
Like, // upvote post or comment
|
||||
|
@ -69,7 +72,7 @@ pub async fn community_inbox(
|
|||
let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
|
||||
|
||||
// Do nothing if we received the same activity before
|
||||
let activity_id = get_activity_id(&activity, &actor.actor_id()?)?;
|
||||
let activity_id = get_activity_id(&activity, &actor.actor_id())?;
|
||||
if is_activity_already_known(context.pool(), &activity_id).await? {
|
||||
return Ok(HttpResponse::Ok().finish());
|
||||
}
|
||||
|
@ -81,7 +84,7 @@ pub async fn community_inbox(
|
|||
})
|
||||
.await??;
|
||||
let to_and_cc = get_activity_to_and_cc(&activity);
|
||||
if !to_and_cc.contains(&&community.actor_id()?) {
|
||||
if !to_and_cc.contains(&&community.actor_id()) {
|
||||
return Err(anyhow!("Activity delivered to wrong community").into());
|
||||
}
|
||||
|
||||
|
@ -92,7 +95,7 @@ pub async fn community_inbox(
|
|||
"Community {} received activity {:?} from {}",
|
||||
community.name,
|
||||
&activity.id_unchecked(),
|
||||
&actor.actor_id_str()
|
||||
&actor.actor_id()
|
||||
);
|
||||
|
||||
community_receive_message(
|
||||
|
@ -113,21 +116,21 @@ pub(crate) async fn community_receive_message(
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<HttpResponse, LemmyError> {
|
||||
// Only users can send activities to the community, so we can get the actor as user
|
||||
// Only persons can send activities to the community, so we can get the actor as person
|
||||
// unconditionally.
|
||||
let actor_id = actor.actor_id_str();
|
||||
let user = blocking(&context.pool(), move |conn| {
|
||||
User_::read_from_apub_id(&conn, &actor_id)
|
||||
let actor_id = actor.actor_id();
|
||||
let person = blocking(&context.pool(), move |conn| {
|
||||
Person::read_from_apub_id(&conn, &actor_id.into())
|
||||
})
|
||||
.await??;
|
||||
check_community_or_site_ban(&user, &to_community, context.pool()).await?;
|
||||
check_community_or_site_ban(&person, to_community.id, context.pool()).await?;
|
||||
|
||||
let any_base = activity.clone().into_any_base()?;
|
||||
let actor_url = actor.actor_id()?;
|
||||
let actor_url = actor.actor_id();
|
||||
let activity_kind = activity.kind().context(location_info!())?;
|
||||
let do_announce = match activity_kind {
|
||||
CommunityValidTypes::Follow => {
|
||||
handle_follow(any_base.clone(), user, &to_community, &context).await?;
|
||||
handle_follow(any_base.clone(), person, &to_community, &context).await?;
|
||||
false
|
||||
}
|
||||
CommunityValidTypes::Undo => {
|
||||
|
@ -162,7 +165,7 @@ pub(crate) async fn community_receive_message(
|
|||
}
|
||||
CommunityValidTypes::Remove => {
|
||||
// TODO: we dont support remote mods, so this is ignored for now
|
||||
//receive_remove_for_community(context, any_base.clone(), &user_url).await?
|
||||
//receive_remove_for_community(context, any_base.clone(), &person_url).await?
|
||||
false
|
||||
}
|
||||
};
|
||||
|
@ -178,20 +181,20 @@ pub(crate) async fn community_receive_message(
|
|||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
/// Handle a follow request from a remote user, adding the user as follower and returning an
|
||||
/// Handle a follow request from a remote person, adding the person as follower and returning an
|
||||
/// Accept activity.
|
||||
async fn handle_follow(
|
||||
activity: AnyBase,
|
||||
user: User_,
|
||||
person: Person,
|
||||
community: &Community,
|
||||
context: &LemmyContext,
|
||||
) -> Result<HttpResponse, LemmyError> {
|
||||
let follow = Follow::from_any_base(activity)?.context(location_info!())?;
|
||||
verify_activity_domains_valid(&follow, &user.actor_id()?, false)?;
|
||||
verify_activity_domains_valid(&follow, &person.actor_id(), false)?;
|
||||
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: community.id,
|
||||
user_id: user.id,
|
||||
person_id: person.id,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
|
@ -226,27 +229,27 @@ async fn handle_undo(
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle `Undo/Follow` from a user, removing the user from followers list.
|
||||
/// Handle `Undo/Follow` from a person, removing the person from followers list.
|
||||
async fn handle_undo_follow(
|
||||
activity: AnyBase,
|
||||
user_url: Url,
|
||||
person_url: Url,
|
||||
community: &Community,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let undo = Undo::from_any_base(activity)?.context(location_info!())?;
|
||||
verify_activity_domains_valid(&undo, &user_url, true)?;
|
||||
verify_activity_domains_valid(&undo, &person_url, true)?;
|
||||
|
||||
let object = undo.object().to_owned().one().context(location_info!())?;
|
||||
let follow = Follow::from_any_base(object)?.context(location_info!())?;
|
||||
verify_activity_domains_valid(&follow, &user_url, false)?;
|
||||
verify_activity_domains_valid(&follow, &person_url, false)?;
|
||||
|
||||
let user = blocking(&context.pool(), move |conn| {
|
||||
User_::read_from_apub_id(&conn, user_url.as_str())
|
||||
let person = blocking(&context.pool(), move |conn| {
|
||||
Person::read_from_apub_id(&conn, &person_url.into())
|
||||
})
|
||||
.await??;
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: community.id,
|
||||
user_id: user.id,
|
||||
person_id: person.id,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
|
@ -260,18 +263,18 @@ async fn handle_undo_follow(
|
|||
}
|
||||
|
||||
pub(crate) async fn check_community_or_site_ban(
|
||||
user: &User_,
|
||||
community: &Community,
|
||||
person: &Person,
|
||||
community_id: CommunityId,
|
||||
pool: &DbPool,
|
||||
) -> Result<(), LemmyError> {
|
||||
if user.banned {
|
||||
return Err(anyhow!("User is banned from site").into());
|
||||
if person.banned {
|
||||
return Err(anyhow!("Person is banned from site").into());
|
||||
}
|
||||
let user_id = user.id;
|
||||
let community_id = community.id;
|
||||
let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
|
||||
let person_id = person.id;
|
||||
let is_banned =
|
||||
move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
|
||||
if blocking(pool, is_banned).await? {
|
||||
return Err(anyhow!("User is banned from community").into());
|
||||
return Err(anyhow!("Person is banned from community").into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -12,18 +12,23 @@ use activitystreams::{
|
|||
};
|
||||
use actix_web::HttpRequest;
|
||||
use anyhow::{anyhow, Context};
|
||||
use lemmy_db_queries::{source::activity::Activity_, ApubObject, DbPool};
|
||||
use lemmy_db_schema::source::{activity::Activity, community::Community, user::User_};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{location_info, settings::Settings, LemmyError};
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{
|
||||
source::{activity::Activity_, community::Community_},
|
||||
ApubObject,
|
||||
DbPool,
|
||||
};
|
||||
use lemmy_db_schema::source::{activity::Activity, community::Community, person::Person};
|
||||
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{export::fmt::Debug, Serialize};
|
||||
use serde::Serialize;
|
||||
use std::fmt::Debug;
|
||||
use url::Url;
|
||||
|
||||
pub mod community_inbox;
|
||||
pub mod person_inbox;
|
||||
mod receive_for_community;
|
||||
pub mod shared_inbox;
|
||||
pub mod user_inbox;
|
||||
|
||||
pub(crate) fn get_activity_id<T, Kind>(activity: &T, creator_uri: &Url) -> Result<Url, LemmyError>
|
||||
where
|
||||
|
@ -40,7 +45,7 @@ pub(crate) async fn is_activity_already_known(
|
|||
pool: &DbPool,
|
||||
activity_id: &Url,
|
||||
) -> Result<bool, LemmyError> {
|
||||
let activity_id = activity_id.to_string();
|
||||
let activity_id = activity_id.to_owned().into();
|
||||
let existing = blocking(pool, move |conn| {
|
||||
Activity::read_from_apub_id(&conn, &activity_id)
|
||||
})
|
||||
|
@ -114,14 +119,17 @@ where
|
|||
}
|
||||
|
||||
/// Returns true if `to_and_cc` contains at least one local user.
|
||||
pub(crate) async fn is_addressed_to_local_user(
|
||||
pub(crate) async fn is_addressed_to_local_person(
|
||||
to_and_cc: &[Url],
|
||||
pool: &DbPool,
|
||||
) -> Result<bool, LemmyError> {
|
||||
for url in to_and_cc {
|
||||
let url = url.to_string();
|
||||
let user = blocking(&pool, move |conn| User_::read_from_apub_id(&conn, &url)).await?;
|
||||
if let Ok(u) = user {
|
||||
let url = url.to_owned();
|
||||
let person = blocking(&pool, move |conn| {
|
||||
Person::read_from_apub_id(&conn, &url.into())
|
||||
})
|
||||
.await?;
|
||||
if let Ok(u) = person {
|
||||
if u.local {
|
||||
return Ok(true);
|
||||
}
|
||||
|
@ -137,16 +145,15 @@ pub(crate) async fn is_addressed_to_community_followers(
|
|||
pool: &DbPool,
|
||||
) -> Result<Option<Community>, LemmyError> {
|
||||
for url in to_and_cc {
|
||||
let url = url.to_string();
|
||||
// TODO: extremely hacky, we should just store the followers url for each community in the db
|
||||
if url.ends_with("/followers") {
|
||||
let community_url = url.replace("/followers", "");
|
||||
let url = url.to_owned().into();
|
||||
let community = blocking(&pool, move |conn| {
|
||||
Community::read_from_apub_id(&conn, &community_url)
|
||||
// ignore errors here, because the current url might not actually be a followers url
|
||||
Community::read_from_followers_url(&conn, &url).ok()
|
||||
})
|
||||
.await??;
|
||||
if !community.local {
|
||||
return Ok(Some(community));
|
||||
.await?;
|
||||
if let Some(c) = community {
|
||||
if !c.local {
|
||||
return Ok(Some(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +167,7 @@ where
|
|||
let id = activity.id_unchecked().context(location_info!())?;
|
||||
let activity_domain = id.domain().context(location_info!())?;
|
||||
|
||||
if activity_domain == Settings::get().hostname {
|
||||
if activity_domain == Settings::get().hostname() {
|
||||
return Err(
|
||||
anyhow!(
|
||||
"Error: received activity which was sent by local instance: {:?}",
|
||||
|
|
|
@ -25,7 +25,7 @@ use crate::{
|
|||
inbox_verify_http_signature,
|
||||
is_activity_already_known,
|
||||
is_addressed_to_community_followers,
|
||||
is_addressed_to_local_user,
|
||||
is_addressed_to_local_person,
|
||||
is_addressed_to_public,
|
||||
receive_for_community::{
|
||||
receive_create_for_community,
|
||||
|
@ -48,24 +48,25 @@ use activitystreams::{
|
|||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use anyhow::{anyhow, Context};
|
||||
use diesel::NotFound;
|
||||
use lemmy_db_queries::{source::user::User, ApubObject, Followable};
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{source::person::Person_, ApubObject, Followable};
|
||||
use lemmy_db_schema::source::{
|
||||
community::{Community, CommunityFollower},
|
||||
person::Person,
|
||||
private_message::PrivateMessage,
|
||||
user::User_,
|
||||
};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{location_info, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use strum_macros::EnumString;
|
||||
use url::Url;
|
||||
|
||||
/// Allowed activities for user inbox.
|
||||
/// Allowed activities for person inbox.
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub enum UserValidTypes {
|
||||
pub enum PersonValidTypes {
|
||||
Accept, // community accepted our follow request
|
||||
Create, // create private message
|
||||
Update, // edit private message
|
||||
|
@ -75,12 +76,12 @@ pub enum UserValidTypes {
|
|||
Announce, // post, comment or vote in community
|
||||
}
|
||||
|
||||
pub type UserAcceptedActivities = ActorAndObject<UserValidTypes>;
|
||||
pub type PersonAcceptedActivities = ActorAndObject<PersonValidTypes>;
|
||||
|
||||
/// Handler for all incoming activities to user inboxes.
|
||||
pub async fn user_inbox(
|
||||
/// Handler for all incoming activities to person inboxes.
|
||||
pub async fn person_inbox(
|
||||
request: HttpRequest,
|
||||
input: web::Json<UserAcceptedActivities>,
|
||||
input: web::Json<PersonAcceptedActivities>,
|
||||
path: web::Path<String>,
|
||||
context: web::Data<LemmyContext>,
|
||||
) -> Result<HttpResponse, LemmyError> {
|
||||
|
@ -90,36 +91,36 @@ pub async fn user_inbox(
|
|||
let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
|
||||
|
||||
// Do nothing if we received the same activity before
|
||||
let activity_id = get_activity_id(&activity, &actor.actor_id()?)?;
|
||||
let activity_id = get_activity_id(&activity, &actor.actor_id())?;
|
||||
if is_activity_already_known(context.pool(), &activity_id).await? {
|
||||
return Ok(HttpResponse::Ok().finish());
|
||||
}
|
||||
|
||||
// Check if the activity is actually meant for us
|
||||
let username = path.into_inner();
|
||||
let user = blocking(&context.pool(), move |conn| {
|
||||
User_::read_from_name(&conn, &username)
|
||||
let person = blocking(&context.pool(), move |conn| {
|
||||
Person::find_by_name(&conn, &username)
|
||||
})
|
||||
.await??;
|
||||
let to_and_cc = get_activity_to_and_cc(&activity);
|
||||
// TODO: we should also accept activities that are sent to community followers
|
||||
if !to_and_cc.contains(&&user.actor_id()?) {
|
||||
return Err(anyhow!("Activity delivered to wrong user").into());
|
||||
if !to_and_cc.contains(&&person.actor_id()) {
|
||||
return Err(anyhow!("Activity delivered to wrong person").into());
|
||||
}
|
||||
|
||||
assert_activity_not_local(&activity)?;
|
||||
insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
|
||||
|
||||
debug!(
|
||||
"User {} received activity {:?} from {}",
|
||||
user.name,
|
||||
"Person {} received activity {:?} from {}",
|
||||
person.name,
|
||||
&activity.id_unchecked(),
|
||||
&actor.actor_id_str()
|
||||
&actor.actor_id()
|
||||
);
|
||||
|
||||
user_receive_message(
|
||||
person_receive_message(
|
||||
activity.clone(),
|
||||
Some(user.clone()),
|
||||
Some(person.clone()),
|
||||
actor.as_ref(),
|
||||
&context,
|
||||
request_counter,
|
||||
|
@ -128,36 +129,43 @@ pub async fn user_inbox(
|
|||
}
|
||||
|
||||
/// Receives Accept/Follow, Announce, private messages and community (undo) remove, (undo) delete
|
||||
pub(crate) async fn user_receive_message(
|
||||
activity: UserAcceptedActivities,
|
||||
to_user: Option<User_>,
|
||||
pub(crate) async fn person_receive_message(
|
||||
activity: PersonAcceptedActivities,
|
||||
to_person: Option<Person>,
|
||||
actor: &dyn ActorType,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<HttpResponse, LemmyError> {
|
||||
is_for_user_inbox(context, &activity).await?;
|
||||
is_for_person_inbox(context, &activity).await?;
|
||||
|
||||
let any_base = activity.clone().into_any_base()?;
|
||||
let kind = activity.kind().context(location_info!())?;
|
||||
let actor_url = actor.actor_id()?;
|
||||
let actor_url = actor.actor_id();
|
||||
match kind {
|
||||
UserValidTypes::Accept => {
|
||||
receive_accept(&context, any_base, actor, to_user.unwrap(), request_counter).await?;
|
||||
PersonValidTypes::Accept => {
|
||||
receive_accept(
|
||||
&context,
|
||||
any_base,
|
||||
actor,
|
||||
to_person.expect("person provided"),
|
||||
request_counter,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
UserValidTypes::Announce => {
|
||||
PersonValidTypes::Announce => {
|
||||
receive_announce(&context, any_base, actor, request_counter).await?
|
||||
}
|
||||
UserValidTypes::Create => {
|
||||
PersonValidTypes::Create => {
|
||||
receive_create(&context, any_base, actor_url, request_counter).await?
|
||||
}
|
||||
UserValidTypes::Update => {
|
||||
PersonValidTypes::Update => {
|
||||
receive_update(&context, any_base, actor_url, request_counter).await?
|
||||
}
|
||||
UserValidTypes::Delete => {
|
||||
PersonValidTypes::Delete => {
|
||||
receive_delete(context, any_base, &actor_url, request_counter).await?
|
||||
}
|
||||
UserValidTypes::Undo => receive_undo(context, any_base, &actor_url, request_counter).await?,
|
||||
UserValidTypes::Remove => receive_remove_community(&context, any_base, &actor_url).await?,
|
||||
PersonValidTypes::Undo => receive_undo(context, any_base, &actor_url, request_counter).await?,
|
||||
PersonValidTypes::Remove => receive_remove_community(&context, any_base, &actor_url).await?,
|
||||
};
|
||||
|
||||
// TODO: would be logical to move websocket notification code here
|
||||
|
@ -165,16 +173,16 @@ pub(crate) async fn user_receive_message(
|
|||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
/// Returns true if the activity is addressed directly to one or more local users, or if it is
|
||||
/// addressed to the followers collection of a remote community, and at least one local user follows
|
||||
/// Returns true if the activity is addressed directly to one or more local persons, or if it is
|
||||
/// addressed to the followers collection of a remote community, and at least one local person follows
|
||||
/// it.
|
||||
async fn is_for_user_inbox(
|
||||
async fn is_for_person_inbox(
|
||||
context: &LemmyContext,
|
||||
activity: &UserAcceptedActivities,
|
||||
activity: &PersonAcceptedActivities,
|
||||
) -> Result<(), LemmyError> {
|
||||
let to_and_cc = get_activity_to_and_cc(activity);
|
||||
// Check if it is addressed directly to any local user
|
||||
if is_addressed_to_local_user(&to_and_cc, context.pool()).await? {
|
||||
// Check if it is addressed directly to any local person
|
||||
if is_addressed_to_local_person(&to_and_cc, context.pool()).await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -197,7 +205,7 @@ async fn is_for_user_inbox(
|
|||
}
|
||||
}
|
||||
|
||||
Err(anyhow!("Not addressed for any local user").into())
|
||||
Err(anyhow!("Not addressed for any local person").into())
|
||||
}
|
||||
|
||||
/// Handle accepted follows.
|
||||
|
@ -205,15 +213,15 @@ async fn receive_accept(
|
|||
context: &LemmyContext,
|
||||
activity: AnyBase,
|
||||
actor: &dyn ActorType,
|
||||
user: User_,
|
||||
person: Person,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let accept = Accept::from_any_base(activity)?.context(location_info!())?;
|
||||
verify_activity_domains_valid(&accept, &actor.actor_id()?, false)?;
|
||||
verify_activity_domains_valid(&accept, &actor.actor_id(), false)?;
|
||||
|
||||
let object = accept.object().to_owned().one().context(location_info!())?;
|
||||
let follow = Follow::from_any_base(object)?.context(location_info!())?;
|
||||
verify_activity_domains_valid(&follow, &user.actor_id()?, false)?;
|
||||
verify_activity_domains_valid(&follow, &person.actor_id(), false)?;
|
||||
|
||||
let community_uri = accept
|
||||
.actor()?
|
||||
|
@ -225,28 +233,42 @@ async fn receive_accept(
|
|||
get_or_fetch_and_upsert_community(&community_uri, context, request_counter).await?;
|
||||
|
||||
let community_id = community.id;
|
||||
let user_id = user.id;
|
||||
let person_id = person.id;
|
||||
// This will throw an error if no follow was requested
|
||||
blocking(&context.pool(), move |conn| {
|
||||
CommunityFollower::follow_accepted(conn, community_id, user_id)
|
||||
CommunityFollower::follow_accepted(conn, community_id, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(EnumString)]
|
||||
enum AnnouncableActivities {
|
||||
Create,
|
||||
Update,
|
||||
Like,
|
||||
Dislike,
|
||||
Delete,
|
||||
Remove,
|
||||
Undo,
|
||||
}
|
||||
|
||||
/// Takes an announce and passes the inner activity to the appropriate handler.
|
||||
async fn receive_announce(
|
||||
pub async fn receive_announce(
|
||||
context: &LemmyContext,
|
||||
activity: AnyBase,
|
||||
actor: &dyn ActorType,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let announce = Announce::from_any_base(activity)?.context(location_info!())?;
|
||||
verify_activity_domains_valid(&announce, &actor.actor_id()?, false)?;
|
||||
verify_activity_domains_valid(&announce, &actor.actor_id(), false)?;
|
||||
is_addressed_to_public(&announce)?;
|
||||
|
||||
let kind = announce.object().as_single_kind_str();
|
||||
let kind = announce
|
||||
.object()
|
||||
.as_single_kind_str()
|
||||
.and_then(|s| s.parse().ok());
|
||||
let inner_activity = announce
|
||||
.object()
|
||||
.to_owned()
|
||||
|
@ -259,22 +281,23 @@ async fn receive_announce(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
use AnnouncableActivities::*;
|
||||
match kind {
|
||||
Some("Create") => {
|
||||
Some(Create) => {
|
||||
receive_create_for_community(context, inner_activity, &inner_id, request_counter).await
|
||||
}
|
||||
Some("Update") => {
|
||||
Some(Update) => {
|
||||
receive_update_for_community(context, inner_activity, &inner_id, request_counter).await
|
||||
}
|
||||
Some("Like") => {
|
||||
Some(Like) => {
|
||||
receive_like_for_community(context, inner_activity, &inner_id, request_counter).await
|
||||
}
|
||||
Some("Dislike") => {
|
||||
Some(Dislike) => {
|
||||
receive_dislike_for_community(context, inner_activity, &inner_id, request_counter).await
|
||||
}
|
||||
Some("Delete") => receive_delete_for_community(context, inner_activity, &inner_id).await,
|
||||
Some("Remove") => receive_remove_for_community(context, inner_activity, &inner_id).await,
|
||||
Some("Undo") => {
|
||||
Some(Delete) => receive_delete_for_community(context, inner_activity, &inner_id).await,
|
||||
Some(Remove) => receive_remove_for_community(context, inner_activity, &inner_id).await,
|
||||
Some(Undo) => {
|
||||
receive_undo_for_community(context, inner_activity, &inner_id, request_counter).await
|
||||
}
|
||||
_ => receive_unhandled_activity(inner_activity),
|
||||
|
@ -375,18 +398,18 @@ async fn find_community_or_private_message_by_id(
|
|||
context: &LemmyContext,
|
||||
apub_id: Url,
|
||||
) -> Result<CommunityOrPrivateMessage, LemmyError> {
|
||||
let ap_id = apub_id.to_string();
|
||||
let ap_id = apub_id.to_owned();
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read_from_apub_id(conn, &ap_id)
|
||||
Community::read_from_apub_id(conn, &ap_id.into())
|
||||
})
|
||||
.await?;
|
||||
if let Ok(c) = community {
|
||||
return Ok(CommunityOrPrivateMessage::Community(c));
|
||||
}
|
||||
|
||||
let ap_id = apub_id.to_string();
|
||||
let ap_id = apub_id.to_owned();
|
||||
let private_message = blocking(context.pool(), move |conn| {
|
||||
PrivateMessage::read_from_apub_id(conn, &ap_id)
|
||||
PrivateMessage::read_from_apub_id(conn, &ap_id.into())
|
||||
})
|
||||
.await?;
|
||||
if let Ok(p) = private_message {
|
|
@ -43,15 +43,22 @@ use activitystreams::{
|
|||
};
|
||||
use anyhow::Context;
|
||||
use diesel::result::Error::NotFound;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::source::site::Site;
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{location_info, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use strum_macros::EnumString;
|
||||
use url::Url;
|
||||
|
||||
#[derive(EnumString)]
|
||||
enum PageOrNote {
|
||||
Page,
|
||||
Note,
|
||||
}
|
||||
|
||||
/// This file is for post/comment activities received by the community, and for post/comment
|
||||
/// activities announced by the community and received by the user.
|
||||
/// activities announced by the community and received by the person.
|
||||
|
||||
/// A post or comment being created
|
||||
pub(in crate::inbox) async fn receive_create_for_community(
|
||||
|
@ -64,9 +71,13 @@ pub(in crate::inbox) async fn receive_create_for_community(
|
|||
verify_activity_domains_valid(&create, &expected_domain, true)?;
|
||||
is_addressed_to_public(&create)?;
|
||||
|
||||
match create.object().as_single_kind_str() {
|
||||
Some("Page") => receive_create_post(create, context, request_counter).await,
|
||||
Some("Note") => receive_create_comment(create, context, request_counter).await,
|
||||
let kind = create
|
||||
.object()
|
||||
.as_single_kind_str()
|
||||
.and_then(|s| s.parse().ok());
|
||||
match kind {
|
||||
Some(PageOrNote::Page) => receive_create_post(create, context, request_counter).await,
|
||||
Some(PageOrNote::Note) => receive_create_comment(create, context, request_counter).await,
|
||||
_ => receive_unhandled_activity(create),
|
||||
}
|
||||
}
|
||||
|
@ -82,9 +93,13 @@ pub(in crate::inbox) async fn receive_update_for_community(
|
|||
verify_activity_domains_valid(&update, &expected_domain, true)?;
|
||||
is_addressed_to_public(&update)?;
|
||||
|
||||
match update.object().as_single_kind_str() {
|
||||
Some("Page") => receive_update_post(update, context, request_counter).await,
|
||||
Some("Note") => receive_update_comment(update, context, request_counter).await,
|
||||
let kind = update
|
||||
.object()
|
||||
.as_single_kind_str()
|
||||
.and_then(|s| s.parse().ok());
|
||||
match kind {
|
||||
Some(PageOrNote::Page) => receive_update_post(update, context, request_counter).await,
|
||||
Some(PageOrNote::Note) => receive_update_comment(update, context, request_counter).await,
|
||||
_ => receive_unhandled_activity(update),
|
||||
}
|
||||
}
|
||||
|
@ -100,11 +115,14 @@ pub(in crate::inbox) async fn receive_like_for_community(
|
|||
verify_activity_domains_valid(&like, &expected_domain, false)?;
|
||||
is_addressed_to_public(&like)?;
|
||||
|
||||
let object_id = get_like_object_id(&like)?;
|
||||
let object_id = like
|
||||
.object()
|
||||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
|
||||
PostOrComment::Post(post) => receive_like_post(like, post, context, request_counter).await,
|
||||
PostOrComment::Post(post) => receive_like_post(like, *post, context, request_counter).await,
|
||||
PostOrComment::Comment(comment) => {
|
||||
receive_like_comment(like, comment, context, request_counter).await
|
||||
receive_like_comment(like, *comment, context, request_counter).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,13 +146,16 @@ pub(in crate::inbox) async fn receive_dislike_for_community(
|
|||
verify_activity_domains_valid(&dislike, &expected_domain, false)?;
|
||||
is_addressed_to_public(&dislike)?;
|
||||
|
||||
let object_id = get_like_object_id(&dislike)?;
|
||||
let object_id = dislike
|
||||
.object()
|
||||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
|
||||
PostOrComment::Post(post) => {
|
||||
receive_dislike_post(dislike, post, context, request_counter).await
|
||||
receive_dislike_post(dislike, *post, context, request_counter).await
|
||||
}
|
||||
PostOrComment::Comment(comment) => {
|
||||
receive_dislike_comment(dislike, comment, context, request_counter).await
|
||||
receive_dislike_comment(dislike, *comment, context, request_counter).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,8 +177,8 @@ pub(in crate::inbox) async fn receive_delete_for_community(
|
|||
.context(location_info!())?;
|
||||
|
||||
match find_post_or_comment_by_id(context, object).await {
|
||||
Ok(PostOrComment::Post(p)) => receive_delete_post(context, p).await,
|
||||
Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, c).await,
|
||||
Ok(PostOrComment::Post(p)) => receive_delete_post(context, *p).await,
|
||||
Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, *c).await,
|
||||
// if we dont have the object, no need to do anything
|
||||
Err(_) => Ok(()),
|
||||
}
|
||||
|
@ -194,13 +215,21 @@ pub(in crate::inbox) async fn receive_remove_for_community(
|
|||
remove.id(community_id.domain().context(location_info!())?)?;
|
||||
|
||||
match find_post_or_comment_by_id(context, object).await {
|
||||
Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, p).await,
|
||||
Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, remove, c).await,
|
||||
Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, *p).await,
|
||||
Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, remove, *c).await,
|
||||
// if we dont have the object, no need to do anything
|
||||
Err(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(EnumString)]
|
||||
enum UndoableActivities {
|
||||
Delete,
|
||||
Remove,
|
||||
Like,
|
||||
Dislike,
|
||||
}
|
||||
|
||||
/// A post/comment action being reverted (either a delete, remove, upvote or downvote)
|
||||
pub(in crate::inbox) async fn receive_undo_for_community(
|
||||
context: &LemmyContext,
|
||||
|
@ -212,13 +241,18 @@ pub(in crate::inbox) async fn receive_undo_for_community(
|
|||
verify_activity_domains_valid(&undo, &expected_domain.to_owned(), true)?;
|
||||
is_addressed_to_public(&undo)?;
|
||||
|
||||
match undo.object().as_single_kind_str() {
|
||||
Some("Delete") => receive_undo_delete_for_community(context, undo, expected_domain).await,
|
||||
Some("Remove") => receive_undo_remove_for_community(context, undo, expected_domain).await,
|
||||
Some("Like") => {
|
||||
use UndoableActivities::*;
|
||||
match undo
|
||||
.object()
|
||||
.as_single_kind_str()
|
||||
.and_then(|s| s.parse().ok())
|
||||
{
|
||||
Some(Delete) => receive_undo_delete_for_community(context, undo, expected_domain).await,
|
||||
Some(Remove) => receive_undo_remove_for_community(context, undo, expected_domain).await,
|
||||
Some(Like) => {
|
||||
receive_undo_like_for_community(context, undo, expected_domain, request_counter).await
|
||||
}
|
||||
Some("Dislike") => {
|
||||
Some(Dislike) => {
|
||||
receive_undo_dislike_for_community(context, undo, expected_domain, request_counter).await
|
||||
}
|
||||
_ => receive_unhandled_activity(undo),
|
||||
|
@ -242,8 +276,8 @@ pub(in crate::inbox) async fn receive_undo_delete_for_community(
|
|||
.single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
match find_post_or_comment_by_id(context, object).await {
|
||||
Ok(PostOrComment::Post(p)) => receive_undo_delete_post(context, p).await,
|
||||
Ok(PostOrComment::Comment(c)) => receive_undo_delete_comment(context, c).await,
|
||||
Ok(PostOrComment::Post(p)) => receive_undo_delete_post(context, *p).await,
|
||||
Ok(PostOrComment::Comment(c)) => receive_undo_delete_comment(context, *c).await,
|
||||
// if we dont have the object, no need to do anything
|
||||
Err(_) => Ok(()),
|
||||
}
|
||||
|
@ -266,8 +300,8 @@ pub(in crate::inbox) async fn receive_undo_remove_for_community(
|
|||
.single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
match find_post_or_comment_by_id(context, object).await {
|
||||
Ok(PostOrComment::Post(p)) => receive_undo_remove_post(context, p).await,
|
||||
Ok(PostOrComment::Comment(c)) => receive_undo_remove_comment(context, c).await,
|
||||
Ok(PostOrComment::Post(p)) => receive_undo_remove_post(context, *p).await,
|
||||
Ok(PostOrComment::Comment(c)) => receive_undo_remove_comment(context, *c).await,
|
||||
// if we dont have the object, no need to do anything
|
||||
Err(_) => Ok(()),
|
||||
}
|
||||
|
@ -285,13 +319,16 @@ pub(in crate::inbox) async fn receive_undo_like_for_community(
|
|||
verify_activity_domains_valid(&like, &expected_domain, false)?;
|
||||
is_addressed_to_public(&like)?;
|
||||
|
||||
let object_id = get_like_object_id(&like)?;
|
||||
let object_id = like
|
||||
.object()
|
||||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
|
||||
PostOrComment::Post(post) => {
|
||||
receive_undo_like_post(&like, post, context, request_counter).await
|
||||
receive_undo_like_post(&like, *post, context, request_counter).await
|
||||
}
|
||||
PostOrComment::Comment(comment) => {
|
||||
receive_undo_like_comment(&like, comment, context, request_counter).await
|
||||
receive_undo_like_comment(&like, *comment, context, request_counter).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -308,13 +345,16 @@ pub(in crate::inbox) async fn receive_undo_dislike_for_community(
|
|||
verify_activity_domains_valid(&dislike, &expected_domain, false)?;
|
||||
is_addressed_to_public(&dislike)?;
|
||||
|
||||
let object_id = get_like_object_id(&dislike)?;
|
||||
let object_id = dislike
|
||||
.object()
|
||||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
|
||||
PostOrComment::Post(post) => {
|
||||
receive_undo_dislike_post(&dislike, post, context, request_counter).await
|
||||
receive_undo_dislike_post(&dislike, *post, context, request_counter).await
|
||||
}
|
||||
PostOrComment::Comment(comment) => {
|
||||
receive_undo_dislike_comment(&dislike, comment, context, request_counter).await
|
||||
receive_undo_dislike_comment(&dislike, *comment, context, request_counter).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -325,35 +365,12 @@ async fn fetch_post_or_comment_by_id(
|
|||
request_counter: &mut i32,
|
||||
) -> Result<PostOrComment, LemmyError> {
|
||||
if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await {
|
||||
return Ok(PostOrComment::Post(post));
|
||||
return Ok(PostOrComment::Post(Box::new(post)));
|
||||
}
|
||||
|
||||
if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await {
|
||||
return Ok(PostOrComment::Comment(comment));
|
||||
return Ok(PostOrComment::Comment(Box::new(comment)));
|
||||
}
|
||||
|
||||
Err(NotFound.into())
|
||||
}
|
||||
|
||||
fn get_like_object_id<Activity>(like_or_dislike: &Activity) -> Result<Url, LemmyError>
|
||||
where
|
||||
Activity: ActorAndObjectRefExt,
|
||||
{
|
||||
// TODO: For backwards compatibility with older Lemmy versions where like.object contains a full
|
||||
// post/comment. This can be removed after some time, using
|
||||
// `activity.oject().as_single_xsd_any_uri()` instead.
|
||||
let object = like_or_dislike.object();
|
||||
if let Some(xsd_uri) = object.as_single_xsd_any_uri() {
|
||||
Ok(xsd_uri.to_owned())
|
||||
} else {
|
||||
Ok(
|
||||
object
|
||||
.to_owned()
|
||||
.one()
|
||||
.context(location_info!())?
|
||||
.id()
|
||||
.context(location_info!())?
|
||||
.to_owned(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,17 +7,17 @@ use crate::{
|
|||
inbox_verify_http_signature,
|
||||
is_activity_already_known,
|
||||
is_addressed_to_community_followers,
|
||||
is_addressed_to_local_user,
|
||||
user_inbox::{user_receive_message, UserAcceptedActivities},
|
||||
is_addressed_to_local_person,
|
||||
person_inbox::{person_receive_message, PersonAcceptedActivities},
|
||||
},
|
||||
insert_activity,
|
||||
};
|
||||
use activitystreams::{activity::ActorAndObject, prelude::*};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use anyhow::Context;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{ApubObject, DbPool};
|
||||
use lemmy_db_schema::source::community::Community;
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{location_info, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -54,7 +54,7 @@ pub async fn shared_inbox(
|
|||
let actor = inbox_verify_http_signature(&activity, &context, request, request_counter).await?;
|
||||
|
||||
// Do nothing if we received the same activity before
|
||||
let actor_id = actor.actor_id()?;
|
||||
let actor_id = actor.actor_id();
|
||||
let activity_id = get_activity_id(&activity, &actor_id)?;
|
||||
if is_activity_already_known(context.pool(), &activity_id).await? {
|
||||
return Ok(HttpResponse::Ok().finish());
|
||||
|
@ -69,9 +69,9 @@ pub async fn shared_inbox(
|
|||
let mut res: Option<HttpResponse> = None;
|
||||
let to_and_cc = get_activity_to_and_cc(&activity);
|
||||
// Handle community first, so in case the sender is banned by the community, it will error out.
|
||||
// If we handled the user receive first, the activity would be inserted to the database before the
|
||||
// If we handled the person receive first, the activity would be inserted to the database before the
|
||||
// community could check for bans.
|
||||
// Note that an activity can be addressed to a community and to a user (or multiple users) at the
|
||||
// Note that an activity can be addressed to a community and to a person (or multiple persons) at the
|
||||
// same time. In this case we still only handle it once, to avoid duplicate websocket
|
||||
// notifications.
|
||||
let community = extract_local_community_from_destinations(&to_and_cc, context.pool()).await?;
|
||||
|
@ -88,13 +88,13 @@ pub async fn shared_inbox(
|
|||
)
|
||||
.await?,
|
||||
);
|
||||
} else if is_addressed_to_local_user(&to_and_cc, context.pool()).await? {
|
||||
let user_activity = UserAcceptedActivities::from_any_base(activity_any_base.clone())?
|
||||
} else if is_addressed_to_local_person(&to_and_cc, context.pool()).await? {
|
||||
let person_activity = PersonAcceptedActivities::from_any_base(activity_any_base.clone())?
|
||||
.context(location_info!())?;
|
||||
// `to_user` is only used for follow activities (which we dont receive here), so no need to pass
|
||||
// `to_person` is only used for follow activities (which we dont receive here), so no need to pass
|
||||
// it in
|
||||
user_receive_message(
|
||||
user_activity,
|
||||
person_receive_message(
|
||||
person_activity,
|
||||
None,
|
||||
actor.as_ref(),
|
||||
&context,
|
||||
|
@ -105,11 +105,11 @@ pub async fn shared_inbox(
|
|||
.await?
|
||||
.is_some()
|
||||
{
|
||||
let user_activity = UserAcceptedActivities::from_any_base(activity_any_base.clone())?
|
||||
let person_activity = PersonAcceptedActivities::from_any_base(activity_any_base.clone())?
|
||||
.context(location_info!())?;
|
||||
res = Some(
|
||||
user_receive_message(
|
||||
user_activity,
|
||||
person_receive_message(
|
||||
person_activity,
|
||||
None,
|
||||
actor.as_ref(),
|
||||
&context,
|
||||
|
@ -137,8 +137,11 @@ async fn extract_local_community_from_destinations(
|
|||
pool: &DbPool,
|
||||
) -> Result<Option<Community>, LemmyError> {
|
||||
for url in to_and_cc {
|
||||
let url = url.to_string();
|
||||
let community = blocking(&pool, move |conn| Community::read_from_apub_id(&conn, &url)).await?;
|
||||
let url = url.to_owned();
|
||||
let community = blocking(&pool, move |conn| {
|
||||
Community::read_from_apub_id(&conn, &url.into())
|
||||
})
|
||||
.await?;
|
||||
if let Ok(c) = community {
|
||||
if c.local {
|
||||
return Ok(Some(c));
|
||||
|
|
|
@ -8,6 +8,7 @@ pub mod fetcher;
|
|||
pub mod http;
|
||||
pub mod inbox;
|
||||
pub mod objects;
|
||||
pub mod routes;
|
||||
|
||||
use crate::extensions::{
|
||||
group_extensions::GroupExtension,
|
||||
|
@ -23,17 +24,20 @@ use activitystreams::{
|
|||
use activitystreams_ext::{Ext1, Ext2};
|
||||
use anyhow::{anyhow, Context};
|
||||
use diesel::NotFound;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{source::activity::Activity_, ApubObject, DbPool};
|
||||
use lemmy_db_schema::source::{
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
activity::Activity,
|
||||
comment::Comment,
|
||||
community::Community,
|
||||
person::Person as DbPerson,
|
||||
post::Post,
|
||||
private_message::PrivateMessage,
|
||||
user::User_,
|
||||
},
|
||||
DbUrl,
|
||||
};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{location_info, settings::Settings, LemmyError};
|
||||
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::Serialize;
|
||||
use std::net::IpAddr;
|
||||
|
@ -41,7 +45,7 @@ use url::{ParseError, Url};
|
|||
|
||||
/// Activitystreams type for community
|
||||
type GroupExt = Ext2<ApActor<ApObject<Group>>, GroupExtension, PublicKeyExtension>;
|
||||
/// Activitystreams type for user
|
||||
/// Activitystreams type for person
|
||||
type PersonExt = Ext1<ApActor<ApObject<Person>>, PublicKeyExtension>;
|
||||
/// Activitystreams type for post
|
||||
type PageExt = Ext1<ApObject<Page>, PageExtension>;
|
||||
|
@ -63,7 +67,7 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
|
|||
let domain = apub_id.domain().context(location_info!())?.to_string();
|
||||
let local_instance = settings.get_hostname_without_port()?;
|
||||
|
||||
if !settings.federation.enabled {
|
||||
if !settings.federation().enabled {
|
||||
return if domain == local_instance {
|
||||
Ok(())
|
||||
} else {
|
||||
|
@ -80,29 +84,30 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
|
|||
let host = apub_id.host_str().context(location_info!())?;
|
||||
let host_as_ip = host.parse::<IpAddr>();
|
||||
if host == "localhost" || host_as_ip.is_ok() {
|
||||
return Err(anyhow!("invalid hostname: {:?}", host).into());
|
||||
return Err(anyhow!("invalid hostname {}: {}", host, apub_id).into());
|
||||
}
|
||||
|
||||
if apub_id.scheme() != Settings::get().get_protocol_string() {
|
||||
return Err(anyhow!("invalid apub id scheme: {:?}", apub_id.scheme()).into());
|
||||
return Err(anyhow!("invalid apub id scheme {}: {}", apub_id.scheme(), apub_id).into());
|
||||
}
|
||||
|
||||
let mut allowed_instances = Settings::get().get_allowed_instances();
|
||||
let allowed_instances = Settings::get().get_allowed_instances();
|
||||
let blocked_instances = Settings::get().get_blocked_instances();
|
||||
if allowed_instances.is_empty() && blocked_instances.is_empty() {
|
||||
|
||||
if allowed_instances.is_none() && blocked_instances.is_none() {
|
||||
Ok(())
|
||||
} else if !allowed_instances.is_empty() {
|
||||
} else if let Some(mut allowed) = allowed_instances {
|
||||
// need to allow this explicitly because apub receive might contain objects from our local
|
||||
// instance. split is needed to remove the port in our federation test setup.
|
||||
allowed_instances.push(local_instance);
|
||||
allowed.push(local_instance);
|
||||
|
||||
if allowed_instances.contains(&domain) {
|
||||
if allowed.contains(&domain) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("{} not in federation allowlist", domain).into())
|
||||
}
|
||||
} else if !blocked_instances.is_empty() {
|
||||
if blocked_instances.contains(&domain) {
|
||||
} else if let Some(blocked) = blocked_instances {
|
||||
if blocked.contains(&domain) {
|
||||
Err(anyhow!("{} is in federation blocklist", domain).into())
|
||||
} else {
|
||||
Ok(())
|
||||
|
@ -116,31 +121,46 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
|
|||
/// and actors in Lemmy.
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait ApubObjectType {
|
||||
async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||
async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||
async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||
async fn send_create(&self, creator: &DbPerson, context: &LemmyContext)
|
||||
-> Result<(), LemmyError>;
|
||||
async fn send_update(&self, creator: &DbPerson, context: &LemmyContext)
|
||||
-> Result<(), LemmyError>;
|
||||
async fn send_delete(&self, creator: &DbPerson, context: &LemmyContext)
|
||||
-> Result<(), LemmyError>;
|
||||
async fn send_undo_delete(
|
||||
&self,
|
||||
creator: &User_,
|
||||
creator: &DbPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError>;
|
||||
async fn send_remove(&self, mod_: &DbPerson, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||
async fn send_undo_remove(
|
||||
&self,
|
||||
mod_: &DbPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError>;
|
||||
async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||
async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait ApubLikeableType {
|
||||
async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||
async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||
async fn send_undo_like(&self, creator: &User_, context: &LemmyContext)
|
||||
-> Result<(), LemmyError>;
|
||||
async fn send_like(&self, creator: &DbPerson, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||
async fn send_dislike(
|
||||
&self,
|
||||
creator: &DbPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError>;
|
||||
async fn send_undo_like(
|
||||
&self,
|
||||
creator: &DbPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError>;
|
||||
}
|
||||
|
||||
/// Common methods provided by ActivityPub actors (community and user). Not all methods are
|
||||
/// Common methods provided by ActivityPub actors (community and person). Not all methods are
|
||||
/// implemented by all actors.
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait ActorType {
|
||||
fn actor_id_str(&self) -> String;
|
||||
fn is_local(&self) -> bool;
|
||||
fn actor_id(&self) -> Url;
|
||||
|
||||
// TODO: every actor should have a public key, so this shouldnt be an option (needs to be fixed in db)
|
||||
fn public_key(&self) -> Option<String>;
|
||||
|
@ -178,17 +198,71 @@ pub trait ActorType {
|
|||
/// For a given community, returns the inboxes of all followers.
|
||||
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
|
||||
|
||||
fn actor_id(&self) -> Result<Url, ParseError> {
|
||||
Url::parse(&self.actor_id_str())
|
||||
fn get_shared_inbox_or_inbox_url(&self) -> Url;
|
||||
|
||||
/// Outbox URL is not generally used by Lemmy, so it can be generated on the fly (but only for
|
||||
/// local actors).
|
||||
fn get_outbox_url(&self) -> Result<Url, LemmyError> {
|
||||
if !self.is_local() {
|
||||
return Err(anyhow!("get_outbox_url() called for remote actor").into());
|
||||
}
|
||||
Ok(Url::parse(&format!("{}/outbox", &self.actor_id()))?)
|
||||
}
|
||||
|
||||
// TODO move these to the db rows
|
||||
fn get_inbox_url(&self) -> Result<Url, ParseError> {
|
||||
Url::parse(&format!("{}/inbox", &self.actor_id_str()))
|
||||
fn get_public_key_ext(&self) -> Result<PublicKeyExtension, LemmyError> {
|
||||
Ok(
|
||||
PublicKey {
|
||||
id: format!("{}#main-key", self.actor_id()),
|
||||
owner: self.actor_id(),
|
||||
public_key_pem: self.public_key().context(location_info!())?,
|
||||
}
|
||||
.to_ext(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_shared_inbox_url(&self) -> Result<Url, LemmyError> {
|
||||
let actor_id = self.actor_id()?;
|
||||
pub enum EndpointType {
|
||||
Community,
|
||||
Person,
|
||||
Post,
|
||||
Comment,
|
||||
PrivateMessage,
|
||||
}
|
||||
|
||||
/// Generates the ActivityPub ID for a given object type and ID.
|
||||
pub fn generate_apub_endpoint(
|
||||
endpoint_type: EndpointType,
|
||||
name: &str,
|
||||
) -> Result<DbUrl, ParseError> {
|
||||
let point = match endpoint_type {
|
||||
EndpointType::Community => "c",
|
||||
EndpointType::Person => "u",
|
||||
EndpointType::Post => "post",
|
||||
EndpointType::Comment => "comment",
|
||||
EndpointType::PrivateMessage => "private_message",
|
||||
};
|
||||
|
||||
Ok(
|
||||
Url::parse(&format!(
|
||||
"{}/{}/{}",
|
||||
Settings::get().get_protocol_and_hostname(),
|
||||
point,
|
||||
name
|
||||
))?
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_followers_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
|
||||
Ok(Url::parse(&format!("{}/followers", actor_id))?.into())
|
||||
}
|
||||
|
||||
pub fn generate_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
|
||||
Ok(Url::parse(&format!("{}/inbox", actor_id))?.into())
|
||||
}
|
||||
|
||||
pub fn generate_shared_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, LemmyError> {
|
||||
let actor_id = actor_id.clone().into_inner();
|
||||
let url = format!(
|
||||
"{}://{}{}/inbox",
|
||||
&actor_id.scheme(),
|
||||
|
@ -199,27 +273,7 @@ pub trait ActorType {
|
|||
"".to_string()
|
||||
},
|
||||
);
|
||||
Ok(Url::parse(&url)?)
|
||||
}
|
||||
|
||||
fn get_outbox_url(&self) -> Result<Url, ParseError> {
|
||||
Url::parse(&format!("{}/outbox", &self.actor_id_str()))
|
||||
}
|
||||
|
||||
fn get_followers_url(&self) -> Result<Url, ParseError> {
|
||||
Url::parse(&format!("{}/followers", &self.actor_id_str()))
|
||||
}
|
||||
|
||||
fn get_public_key_ext(&self) -> Result<PublicKeyExtension, LemmyError> {
|
||||
Ok(
|
||||
PublicKey {
|
||||
id: format!("{}#main-key", self.actor_id_str()),
|
||||
owner: self.actor_id_str(),
|
||||
public_key_pem: self.public_key().context(location_info!())?,
|
||||
}
|
||||
.to_ext(),
|
||||
)
|
||||
}
|
||||
Ok(Url::parse(&url)?.into())
|
||||
}
|
||||
|
||||
/// Store a sent or received activity in the database, for logging purposes. These records are not
|
||||
|
@ -234,7 +288,7 @@ pub(crate) async fn insert_activity<T>(
|
|||
where
|
||||
T: Serialize + std::fmt::Debug + Send + 'static,
|
||||
{
|
||||
let ap_id = ap_id.to_string();
|
||||
let ap_id = ap_id.to_owned().into();
|
||||
blocking(pool, move |conn| {
|
||||
Activity::insert(conn, ap_id, &activity, local, sensitive)
|
||||
})
|
||||
|
@ -243,8 +297,8 @@ where
|
|||
}
|
||||
|
||||
pub(crate) enum PostOrComment {
|
||||
Comment(Comment),
|
||||
Post(Post),
|
||||
Comment(Box<Comment>),
|
||||
Post(Box<Post>),
|
||||
}
|
||||
|
||||
/// Tries to find a post or comment in the local database, without any network requests.
|
||||
|
@ -254,71 +308,71 @@ pub(crate) async fn find_post_or_comment_by_id(
|
|||
context: &LemmyContext,
|
||||
apub_id: Url,
|
||||
) -> Result<PostOrComment, LemmyError> {
|
||||
let ap_id = apub_id.to_string();
|
||||
let ap_id = apub_id.clone();
|
||||
let post = blocking(context.pool(), move |conn| {
|
||||
Post::read_from_apub_id(conn, &ap_id)
|
||||
Post::read_from_apub_id(conn, &ap_id.into())
|
||||
})
|
||||
.await?;
|
||||
if let Ok(p) = post {
|
||||
return Ok(PostOrComment::Post(p));
|
||||
return Ok(PostOrComment::Post(Box::new(p)));
|
||||
}
|
||||
|
||||
let ap_id = apub_id.to_string();
|
||||
let ap_id = apub_id.clone();
|
||||
let comment = blocking(context.pool(), move |conn| {
|
||||
Comment::read_from_apub_id(conn, &ap_id)
|
||||
Comment::read_from_apub_id(conn, &ap_id.into())
|
||||
})
|
||||
.await?;
|
||||
if let Ok(c) = comment {
|
||||
return Ok(PostOrComment::Comment(c));
|
||||
return Ok(PostOrComment::Comment(Box::new(c)));
|
||||
}
|
||||
|
||||
Err(NotFound.into())
|
||||
}
|
||||
|
||||
pub(crate) enum Object {
|
||||
Comment(Comment),
|
||||
Post(Post),
|
||||
Community(Community),
|
||||
User(User_),
|
||||
PrivateMessage(PrivateMessage),
|
||||
Comment(Box<Comment>),
|
||||
Post(Box<Post>),
|
||||
Community(Box<Community>),
|
||||
Person(Box<DbPerson>),
|
||||
PrivateMessage(Box<PrivateMessage>),
|
||||
}
|
||||
|
||||
pub(crate) async fn find_object_by_id(
|
||||
context: &LemmyContext,
|
||||
apub_id: Url,
|
||||
) -> Result<Object, LemmyError> {
|
||||
if let Ok(pc) = find_post_or_comment_by_id(context, apub_id.to_owned()).await {
|
||||
let ap_id = apub_id.clone();
|
||||
if let Ok(pc) = find_post_or_comment_by_id(context, ap_id.to_owned()).await {
|
||||
return Ok(match pc {
|
||||
PostOrComment::Post(p) => Object::Post(p),
|
||||
PostOrComment::Comment(c) => Object::Comment(c),
|
||||
PostOrComment::Post(p) => Object::Post(Box::new(*p)),
|
||||
PostOrComment::Comment(c) => Object::Comment(Box::new(*c)),
|
||||
});
|
||||
}
|
||||
|
||||
let ap_id = apub_id.to_string();
|
||||
let user = blocking(context.pool(), move |conn| {
|
||||
User_::read_from_apub_id(conn, &ap_id)
|
||||
let ap_id = apub_id.clone();
|
||||
let person = blocking(context.pool(), move |conn| {
|
||||
DbPerson::read_from_apub_id(conn, &ap_id.into())
|
||||
})
|
||||
.await?;
|
||||
if let Ok(u) = user {
|
||||
return Ok(Object::User(u));
|
||||
if let Ok(u) = person {
|
||||
return Ok(Object::Person(Box::new(u)));
|
||||
}
|
||||
|
||||
let ap_id = apub_id.to_string();
|
||||
let ap_id = apub_id.clone();
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read_from_apub_id(conn, &ap_id)
|
||||
Community::read_from_apub_id(conn, &ap_id.into())
|
||||
})
|
||||
.await?;
|
||||
if let Ok(c) = community {
|
||||
return Ok(Object::Community(c));
|
||||
return Ok(Object::Community(Box::new(c)));
|
||||
}
|
||||
|
||||
let ap_id = apub_id.to_string();
|
||||
let private_message = blocking(context.pool(), move |conn| {
|
||||
PrivateMessage::read_from_apub_id(conn, &ap_id)
|
||||
PrivateMessage::read_from_apub_id(conn, &apub_id.into())
|
||||
})
|
||||
.await?;
|
||||
if let Ok(pm) = private_message {
|
||||
return Ok(Object::PrivateMessage(pm));
|
||||
return Ok(Object::PrivateMessage(Box::new(pm)));
|
||||
}
|
||||
|
||||
Err(NotFound.into())
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
check_object_for_community_or_site_ban,
|
||||
create_tombstone,
|
||||
get_object_from_apub,
|
||||
get_or_fetch_and_upsert_user,
|
||||
get_or_fetch_and_upsert_person,
|
||||
get_source_markdown_value,
|
||||
set_content_and_source,
|
||||
FromApub,
|
||||
|
@ -18,16 +18,19 @@ use crate::{
|
|||
use activitystreams::{
|
||||
object::{kind::NoteType, ApObject, Note, Tombstone},
|
||||
prelude::*,
|
||||
public,
|
||||
};
|
||||
use anyhow::{anyhow, Context};
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{Crud, DbPool};
|
||||
use lemmy_db_schema::source::{
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
comment::{Comment, CommentForm},
|
||||
community::Community,
|
||||
person::Person,
|
||||
post::Post,
|
||||
user::User_,
|
||||
},
|
||||
CommentId,
|
||||
};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{
|
||||
location_info,
|
||||
utils::{convert_datetime, remove_slurs},
|
||||
|
@ -44,32 +47,29 @@ impl ToApub for Comment {
|
|||
let mut comment = ApObject::new(Note::new());
|
||||
|
||||
let creator_id = self.creator_id;
|
||||
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
|
||||
let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??;
|
||||
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
let community_id = post.community_id;
|
||||
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
|
||||
|
||||
// Add a vector containing some important info to the "in_reply_to" field
|
||||
// [post_ap_id, Option(parent_comment_ap_id)]
|
||||
let mut in_reply_to_vec = vec![post.ap_id];
|
||||
let mut in_reply_to_vec = vec![post.ap_id.into_inner()];
|
||||
|
||||
if let Some(parent_id) = self.parent_id {
|
||||
let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??;
|
||||
|
||||
in_reply_to_vec.push(parent_comment.ap_id);
|
||||
in_reply_to_vec.push(parent_comment.ap_id.into_inner());
|
||||
}
|
||||
|
||||
comment
|
||||
// Not needed when the Post is embedded in a collection (like for community outbox)
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(Url::parse(&self.ap_id)?)
|
||||
.set_id(self.ap_id.to_owned().into_inner())
|
||||
.set_published(convert_datetime(self.published))
|
||||
.set_to(community.actor_id)
|
||||
.set_to(public())
|
||||
.set_many_in_reply_tos(in_reply_to_vec)
|
||||
.set_attributed_to(creator.actor_id);
|
||||
.set_attributed_to(creator.actor_id.into_inner());
|
||||
|
||||
set_content_and_source(&mut comment, &self.content)?;
|
||||
|
||||
|
@ -81,7 +81,12 @@ impl ToApub for Comment {
|
|||
}
|
||||
|
||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||
create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
|
||||
create_tombstone(
|
||||
self.deleted,
|
||||
self.ap_id.to_owned().into(),
|
||||
self.updated,
|
||||
NoteType::Note,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,13 +103,13 @@ impl FromApub for Comment {
|
|||
expected_domain: Url,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<Comment, LemmyError> {
|
||||
check_object_for_community_or_site_ban(note, context, request_counter).await?;
|
||||
|
||||
let comment: Comment =
|
||||
get_object_from_apub(note, context, expected_domain, request_counter).await?;
|
||||
|
||||
let post_id = comment.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
check_object_for_community_or_site_ban(note, post.community_id, context, request_counter)
|
||||
.await?;
|
||||
if post.locked {
|
||||
// This is not very efficient because a comment gets inserted just to be deleted right
|
||||
// afterwards, but it seems to be the easiest way to implement it.
|
||||
|
@ -133,7 +138,8 @@ impl FromApubToForm<NoteExt> for CommentForm {
|
|||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
|
||||
let creator = get_or_fetch_and_upsert_user(creator_actor_id, context, request_counter).await?;
|
||||
let creator =
|
||||
get_or_fetch_and_upsert_person(creator_actor_id, context, request_counter).await?;
|
||||
|
||||
let mut in_reply_tos = note
|
||||
.in_reply_to()
|
||||
|
@ -150,7 +156,7 @@ impl FromApubToForm<NoteExt> for CommentForm {
|
|||
|
||||
// The 2nd item, if it exists, is the parent comment apub_id
|
||||
// For deeply nested comments, FromApub automatically gets called recursively
|
||||
let parent_id: Option<i32> = match in_reply_tos.next() {
|
||||
let parent_id: Option<CommentId> = match in_reply_tos.next() {
|
||||
Some(parent_comment_uri) => {
|
||||
let parent_comment_ap_id = &parent_comment_uri?;
|
||||
let parent_comment =
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
extensions::{context::lemmy_context, group_extensions::GroupExtension},
|
||||
fetcher::user::get_or_fetch_and_upsert_user,
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
objects::{
|
||||
check_object_domain,
|
||||
create_tombstone,
|
||||
|
@ -22,13 +22,13 @@ use activitystreams::{
|
|||
};
|
||||
use activitystreams_ext::Ext2;
|
||||
use anyhow::Context;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::DbPool;
|
||||
use lemmy_db_schema::{
|
||||
naive_now,
|
||||
source::community::{Community, CommunityForm},
|
||||
};
|
||||
use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{
|
||||
location_info,
|
||||
utils::{check_slurs, check_slurs_opt, convert_datetime},
|
||||
|
@ -51,15 +51,15 @@ impl ToApub for Community {
|
|||
CommunityModeratorView::for_community(&conn, id)
|
||||
})
|
||||
.await??;
|
||||
let moderators: Vec<String> = moderators
|
||||
let moderators: Vec<Url> = moderators
|
||||
.into_iter()
|
||||
.map(|m| m.moderator.actor_id)
|
||||
.map(|m| m.moderator.actor_id.into_inner())
|
||||
.collect();
|
||||
|
||||
let mut group = ApObject::new(Group::new());
|
||||
group
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(Url::parse(&self.actor_id)?)
|
||||
.set_id(self.actor_id.to_owned().into())
|
||||
.set_name(self.title.to_owned())
|
||||
.set_published(convert_datetime(self.published))
|
||||
.set_many_attributed_tos(moderators);
|
||||
|
@ -73,42 +73,40 @@ impl ToApub for Community {
|
|||
|
||||
if let Some(icon_url) = &self.icon {
|
||||
let mut image = Image::new();
|
||||
image.set_url(Url::parse(icon_url)?);
|
||||
image.set_url::<Url>(icon_url.to_owned().into());
|
||||
group.set_icon(image.into_any_base()?);
|
||||
}
|
||||
|
||||
if let Some(banner_url) = &self.banner {
|
||||
let mut image = Image::new();
|
||||
image.set_url(Url::parse(banner_url)?);
|
||||
image.set_url::<Url>(banner_url.to_owned().into());
|
||||
group.set_image(image.into_any_base()?);
|
||||
}
|
||||
|
||||
let mut ap_actor = ApActor::new(self.get_inbox_url()?, group);
|
||||
let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), group);
|
||||
ap_actor
|
||||
.set_preferred_username(self.name.to_owned())
|
||||
.set_outbox(self.get_outbox_url()?)
|
||||
.set_followers(self.get_followers_url()?)
|
||||
.set_followers(self.followers_url.clone().into())
|
||||
.set_endpoints(Endpoints {
|
||||
shared_inbox: Some(self.get_shared_inbox_url()?),
|
||||
shared_inbox: Some(self.get_shared_inbox_or_inbox_url()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let nsfw = self.nsfw;
|
||||
let category_id = self.category_id;
|
||||
let group_extension = blocking(pool, move |conn| {
|
||||
GroupExtension::new(conn, category_id, nsfw)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Ext2::new(
|
||||
ap_actor,
|
||||
group_extension,
|
||||
GroupExtension::new(self.nsfw)?,
|
||||
self.get_public_key_ext()?,
|
||||
))
|
||||
}
|
||||
|
||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||
create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group)
|
||||
create_tombstone(
|
||||
self.deleted,
|
||||
self.actor_id.to_owned().into(),
|
||||
self.updated,
|
||||
GroupType::Group,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +143,7 @@ impl FromApubToForm<GroupExt> for CommunityForm {
|
|||
.as_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
|
||||
let creator = get_or_fetch_and_upsert_user(creator_uri, context, request_counter).await?;
|
||||
let creator = get_or_fetch_and_upsert_person(creator_uri, context, request_counter).await?;
|
||||
let name = group
|
||||
.inner
|
||||
.preferred_username()
|
||||
|
@ -175,11 +173,10 @@ impl FromApubToForm<GroupExt> for CommunityForm {
|
|||
.url()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.map(|u| u.to_string()),
|
||||
.map(|u| u.to_owned().into()),
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let banner = match group.image() {
|
||||
Some(any_image) => Some(
|
||||
Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
|
||||
|
@ -188,22 +185,27 @@ impl FromApubToForm<GroupExt> for CommunityForm {
|
|||
.url()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.map(|u| u.to_string()),
|
||||
.map(|u| u.to_owned().into()),
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let shared_inbox = group
|
||||
.inner
|
||||
.endpoints()?
|
||||
.map(|e| e.shared_inbox)
|
||||
.flatten()
|
||||
.map(|s| s.to_owned().into());
|
||||
|
||||
Ok(CommunityForm {
|
||||
name,
|
||||
title,
|
||||
description,
|
||||
category_id: group.ext_one.category.identifier.parse::<i32>()?,
|
||||
creator_id: creator.id,
|
||||
removed: None,
|
||||
published: group.inner.published().map(|u| u.to_owned().naive_local()),
|
||||
updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
|
||||
deleted: None,
|
||||
nsfw: group.ext_one.sensitive,
|
||||
nsfw: group.ext_one.sensitive.unwrap_or(false),
|
||||
actor_id: Some(check_object_domain(group, expected_domain)?),
|
||||
local: false,
|
||||
private_key: None,
|
||||
|
@ -211,6 +213,16 @@ impl FromApubToForm<GroupExt> for CommunityForm {
|
|||
last_refreshed_at: Some(naive_now()),
|
||||
icon,
|
||||
banner,
|
||||
followers_url: Some(
|
||||
group
|
||||
.inner
|
||||
.followers()?
|
||||
.context(location_info!())?
|
||||
.to_owned()
|
||||
.into(),
|
||||
),
|
||||
inbox_url: Some(group.inner.inbox()?.to_owned().into()),
|
||||
shared_inbox_url: Some(shared_inbox),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
check_is_apub_id_valid,
|
||||
fetcher::{community::get_or_fetch_and_upsert_community, user::get_or_fetch_and_upsert_user},
|
||||
fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person},
|
||||
inbox::community_inbox::check_community_or_site_ban,
|
||||
};
|
||||
use activitystreams::{
|
||||
|
@ -11,17 +11,24 @@ use activitystreams::{
|
|||
};
|
||||
use anyhow::{anyhow, Context};
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::result::Error::NotFound;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{ApubObject, Crud, DbPool};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{location_info, settings::Settings, utils::convert_datetime, LemmyError};
|
||||
use lemmy_db_schema::{source::community::Community, CommunityId, DbUrl};
|
||||
use lemmy_utils::{
|
||||
location_info,
|
||||
settings::structs::Settings,
|
||||
utils::{convert_datetime, markdown_to_html},
|
||||
LemmyError,
|
||||
};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use url::Url;
|
||||
|
||||
pub(crate) mod comment;
|
||||
pub(crate) mod community;
|
||||
pub(crate) mod person;
|
||||
pub(crate) mod post;
|
||||
pub(crate) mod private_message;
|
||||
pub(crate) mod user;
|
||||
|
||||
/// Trait for converting an object or actor into the respective ActivityPub type.
|
||||
#[async_trait::async_trait(?Send)]
|
||||
|
@ -64,7 +71,7 @@ pub(in crate::objects) trait FromApubToForm<ApubType> {
|
|||
/// Updated is actually the deletion time
|
||||
fn create_tombstone<T>(
|
||||
deleted: bool,
|
||||
object_id: &str,
|
||||
object_id: Url,
|
||||
updated: Option<NaiveDateTime>,
|
||||
former_type: T,
|
||||
) -> Result<Tombstone, LemmyError>
|
||||
|
@ -74,7 +81,7 @@ where
|
|||
if deleted {
|
||||
if let Some(updated) = updated {
|
||||
let mut tombstone = Tombstone::new();
|
||||
tombstone.set_id(object_id.parse()?);
|
||||
tombstone.set_id(object_id);
|
||||
tombstone.set_former_type(former_type.to_string());
|
||||
tombstone.set_deleted(convert_datetime(updated));
|
||||
Ok(tombstone)
|
||||
|
@ -89,14 +96,14 @@ where
|
|||
pub(in crate::objects) fn check_object_domain<T, Kind>(
|
||||
apub: &T,
|
||||
expected_domain: Url,
|
||||
) -> Result<String, LemmyError>
|
||||
) -> Result<DbUrl, LemmyError>
|
||||
where
|
||||
T: Base + AsBase<Kind>,
|
||||
{
|
||||
let domain = expected_domain.domain().context(location_info!())?;
|
||||
let object_id = apub.id(domain)?.context(location_info!())?;
|
||||
check_is_apub_id_valid(&object_id)?;
|
||||
Ok(object_id.to_string())
|
||||
check_is_apub_id_valid(object_id)?;
|
||||
Ok(object_id.to_owned().into())
|
||||
}
|
||||
|
||||
pub(in crate::objects) fn set_content_and_source<T, Kind1, Kind2>(
|
||||
|
@ -112,11 +119,8 @@ where
|
|||
.set_media_type(mime_markdown()?);
|
||||
object.set_source(source.into_any_base()?);
|
||||
|
||||
// set `content` to markdown for compatibility with older Lemmy versions
|
||||
// TODO: change this to HTML in a while
|
||||
object.set_content(markdown_text);
|
||||
object.set_media_type(mime_markdown()?);
|
||||
//object.set_content(markdown_to_html(markdown_text));
|
||||
object.set_content(markdown_to_html(markdown_text));
|
||||
object.set_media_type(mime_html()?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -132,9 +136,7 @@ where
|
|||
.flatten()
|
||||
.map(|s| s.to_string());
|
||||
if content.is_some() {
|
||||
let source = object.source();
|
||||
// updated lemmy version, read markdown from `source.content`
|
||||
if let Some(source) = source {
|
||||
let source = object.source().context(location_info!())?;
|
||||
let source = Object::<()>::from_any_base(source.to_owned())?.context(location_info!())?;
|
||||
check_is_markdown(source.media_type())?;
|
||||
let source_content = source
|
||||
|
@ -145,19 +147,17 @@ where
|
|||
.to_string();
|
||||
return Ok(Some(source_content));
|
||||
}
|
||||
// older lemmy version, read markdown from `content`
|
||||
// TODO: remove this after a while
|
||||
else {
|
||||
return Ok(content);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(in crate::objects) fn mime_markdown() -> Result<Mime, FromStrError> {
|
||||
fn mime_markdown() -> Result<Mime, FromStrError> {
|
||||
"text/markdown".parse()
|
||||
}
|
||||
|
||||
fn mime_html() -> Result<Mime, FromStrError> {
|
||||
"text/html".parse()
|
||||
}
|
||||
|
||||
pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), LemmyError> {
|
||||
let mime = mime.context(location_info!())?;
|
||||
if !mime.eq(&mime_markdown()?) {
|
||||
|
@ -172,7 +172,7 @@ pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), L
|
|||
/// Converts an ActivityPub object (eg `Note`) to a database object (eg `Comment`). If an object
|
||||
/// with the same ActivityPub ID already exists in the database, it is returned directly. Otherwise
|
||||
/// the apub object is parsed, inserted and returned.
|
||||
pub(in crate::objects) async fn get_object_from_apub<From, Kind, To, ToForm>(
|
||||
pub(in crate::objects) async fn get_object_from_apub<From, Kind, To, ToForm, IdType>(
|
||||
from: &From,
|
||||
context: &LemmyContext,
|
||||
expected_domain: Url,
|
||||
|
@ -180,16 +180,16 @@ pub(in crate::objects) async fn get_object_from_apub<From, Kind, To, ToForm>(
|
|||
) -> Result<To, LemmyError>
|
||||
where
|
||||
From: BaseExt<Kind>,
|
||||
To: ApubObject<ToForm> + Crud<ToForm> + Send + 'static,
|
||||
To: ApubObject<ToForm> + Crud<ToForm, IdType> + Send + 'static,
|
||||
ToForm: FromApubToForm<From> + Send + 'static,
|
||||
{
|
||||
let object_id = from.id_unchecked().context(location_info!())?.to_owned();
|
||||
let domain = object_id.domain().context(location_info!())?;
|
||||
|
||||
// if its a local object, return it directly from the database
|
||||
if Settings::get().hostname == domain {
|
||||
if Settings::get().hostname() == domain {
|
||||
let object = blocking(context.pool(), move |conn| {
|
||||
To::read_from_apub_id(conn, object_id.as_str())
|
||||
To::read_from_apub_id(conn, &object_id.into())
|
||||
})
|
||||
.await??;
|
||||
Ok(object)
|
||||
|
@ -205,23 +205,43 @@ where
|
|||
|
||||
pub(in crate::objects) async fn check_object_for_community_or_site_ban<T, Kind>(
|
||||
object: &T,
|
||||
community_id: CommunityId,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError>
|
||||
where
|
||||
T: ObjectExt<Kind>,
|
||||
{
|
||||
let user_id = object
|
||||
let person_id = object
|
||||
.attributed_to()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
let user = get_or_fetch_and_upsert_user(user_id, context, request_counter).await?;
|
||||
let community_id = object
|
||||
let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?;
|
||||
check_community_or_site_ban(&person, community_id, context.pool()).await
|
||||
}
|
||||
|
||||
pub(in crate::objects) async fn get_to_community<T, Kind>(
|
||||
object: &T,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<Community, LemmyError>
|
||||
where
|
||||
T: ObjectExt<Kind>,
|
||||
{
|
||||
let community_ids = object
|
||||
.to()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
let community = get_or_fetch_and_upsert_community(community_id, context, request_counter).await?;
|
||||
check_community_or_site_ban(&user, &community, context.pool()).await
|
||||
.as_many()
|
||||
.context(location_info!())?
|
||||
.iter()
|
||||
.map(|a| a.as_xsd_any_uri().context(location_info!()))
|
||||
.collect::<Result<Vec<&Url>, anyhow::Error>>()?;
|
||||
for cid in community_ids {
|
||||
let community = get_or_fetch_and_upsert_community(&cid, context, request_counter).await;
|
||||
if community.is_ok() {
|
||||
return community;
|
||||
}
|
||||
}
|
||||
Err(NotFound.into())
|
||||
}
|
||||
|
|
|
@ -18,15 +18,15 @@ use activitystreams::{
|
|||
};
|
||||
use activitystreams_ext::Ext1;
|
||||
use anyhow::Context;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{ApubObject, DbPool};
|
||||
use lemmy_db_schema::{
|
||||
naive_now,
|
||||
source::user::{UserForm, User_},
|
||||
source::person::{Person as DbPerson, PersonForm},
|
||||
};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{
|
||||
location_info,
|
||||
settings::Settings,
|
||||
settings::structs::Settings,
|
||||
utils::{check_slurs, check_slurs_opt, convert_datetime},
|
||||
LemmyError,
|
||||
};
|
||||
|
@ -34,14 +34,14 @@ use lemmy_websocket::LemmyContext;
|
|||
use url::Url;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ToApub for User_ {
|
||||
impl ToApub for DbPerson {
|
||||
type ApubType = PersonExt;
|
||||
|
||||
async fn to_apub(&self, _pool: &DbPool) -> Result<PersonExt, LemmyError> {
|
||||
let mut person = ApObject::new(Person::new());
|
||||
person
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(Url::parse(&self.actor_id)?)
|
||||
.set_id(self.actor_id.to_owned().into_inner())
|
||||
.set_published(convert_datetime(self.published));
|
||||
|
||||
if let Some(u) = self.updated {
|
||||
|
@ -50,33 +50,30 @@ impl ToApub for User_ {
|
|||
|
||||
if let Some(avatar_url) = &self.avatar {
|
||||
let mut image = Image::new();
|
||||
image.set_url(Url::parse(avatar_url)?);
|
||||
image.set_url::<Url>(avatar_url.to_owned().into());
|
||||
person.set_icon(image.into_any_base()?);
|
||||
}
|
||||
|
||||
if let Some(banner_url) = &self.banner {
|
||||
let mut image = Image::new();
|
||||
image.set_url(Url::parse(banner_url)?);
|
||||
image.set_url::<Url>(banner_url.to_owned().into());
|
||||
person.set_image(image.into_any_base()?);
|
||||
}
|
||||
|
||||
if let Some(bio) = &self.bio {
|
||||
set_content_and_source(&mut person, bio)?;
|
||||
// Also set summary for compatibility with older Lemmy versions.
|
||||
// TODO: remove this after a while.
|
||||
person.set_summary(bio.to_owned());
|
||||
}
|
||||
|
||||
if let Some(i) = self.preferred_username.to_owned() {
|
||||
person.set_name(i);
|
||||
}
|
||||
|
||||
let mut ap_actor = ApActor::new(self.get_inbox_url()?, person);
|
||||
let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), person);
|
||||
ap_actor
|
||||
.set_preferred_username(self.name.to_owned())
|
||||
.set_outbox(self.get_outbox_url()?)
|
||||
.set_endpoints(Endpoints {
|
||||
shared_inbox: Some(self.get_shared_inbox_url()?),
|
||||
shared_inbox: Some(self.get_shared_inbox_or_inbox_url()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
|
@ -88,7 +85,7 @@ impl ToApub for User_ {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FromApub for User_ {
|
||||
impl FromApub for DbPerson {
|
||||
type ApubType = PersonExt;
|
||||
|
||||
async fn from_apub(
|
||||
|
@ -96,26 +93,29 @@ impl FromApub for User_ {
|
|||
context: &LemmyContext,
|
||||
expected_domain: Url,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<User_, LemmyError> {
|
||||
let user_id = person.id_unchecked().context(location_info!())?.to_owned();
|
||||
let domain = user_id.domain().context(location_info!())?;
|
||||
if domain == Settings::get().hostname {
|
||||
let user = blocking(context.pool(), move |conn| {
|
||||
User_::read_from_apub_id(conn, user_id.as_str())
|
||||
) -> Result<DbPerson, LemmyError> {
|
||||
let person_id = person.id_unchecked().context(location_info!())?.to_owned();
|
||||
let domain = person_id.domain().context(location_info!())?;
|
||||
if domain == Settings::get().hostname() {
|
||||
let person = blocking(context.pool(), move |conn| {
|
||||
DbPerson::read_from_apub_id(conn, &person_id.into())
|
||||
})
|
||||
.await??;
|
||||
Ok(user)
|
||||
Ok(person)
|
||||
} else {
|
||||
let user_form =
|
||||
UserForm::from_apub(person, context, expected_domain, request_counter).await?;
|
||||
let user = blocking(context.pool(), move |conn| User_::upsert(conn, &user_form)).await??;
|
||||
Ok(user)
|
||||
let person_form =
|
||||
PersonForm::from_apub(person, context, expected_domain, request_counter).await?;
|
||||
let person = blocking(context.pool(), move |conn| {
|
||||
DbPerson::upsert(conn, &person_form)
|
||||
})
|
||||
.await??;
|
||||
Ok(person)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FromApubToForm<PersonExt> for UserForm {
|
||||
impl FromApubToForm<PersonExt> for PersonForm {
|
||||
async fn from_apub(
|
||||
person: &PersonExt,
|
||||
_context: &LemmyContext,
|
||||
|
@ -129,7 +129,7 @@ impl FromApubToForm<PersonExt> for UserForm {
|
|||
.url()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.map(|u| u.to_string()),
|
||||
.map(|url| url.to_owned()),
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
@ -142,7 +142,7 @@ impl FromApubToForm<PersonExt> for UserForm {
|
|||
.url()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.map(|u| u.to_string()),
|
||||
.map(|url| url.to_owned()),
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
@ -158,38 +158,35 @@ impl FromApubToForm<PersonExt> for UserForm {
|
|||
.flatten()
|
||||
.map(|n| n.to_owned().xsd_string())
|
||||
.flatten();
|
||||
|
||||
let bio = get_source_markdown_value(person)?;
|
||||
let shared_inbox = person
|
||||
.inner
|
||||
.endpoints()?
|
||||
.map(|e| e.shared_inbox)
|
||||
.flatten()
|
||||
.map(|s| s.to_owned().into());
|
||||
|
||||
check_slurs(&name)?;
|
||||
check_slurs_opt(&preferred_username)?;
|
||||
check_slurs_opt(&bio)?;
|
||||
|
||||
Ok(UserForm {
|
||||
Ok(PersonForm {
|
||||
name,
|
||||
preferred_username: Some(preferred_username),
|
||||
password_encrypted: "".to_string(),
|
||||
admin: false,
|
||||
banned: None,
|
||||
email: None,
|
||||
avatar,
|
||||
banner,
|
||||
deleted: None,
|
||||
avatar: avatar.map(|o| o.map(|i| i.into())),
|
||||
banner: banner.map(|o| o.map(|i| i.into())),
|
||||
published: person.inner.published().map(|u| u.to_owned().naive_local()),
|
||||
updated: person.updated().map(|u| u.to_owned().naive_local()),
|
||||
show_nsfw: false,
|
||||
theme: "".to_string(),
|
||||
default_sort_type: 0,
|
||||
default_listing_type: 0,
|
||||
lang: "".to_string(),
|
||||
show_avatars: false,
|
||||
send_notifications_to_email: false,
|
||||
matrix_user_id: None,
|
||||
actor_id: Some(check_object_domain(person, expected_domain)?),
|
||||
bio: Some(bio),
|
||||
local: false,
|
||||
local: Some(false),
|
||||
private_key: None,
|
||||
public_key: Some(person.ext_one.public_key.to_owned().public_key_pem),
|
||||
public_key: Some(Some(person.ext_one.public_key.to_owned().public_key_pem)),
|
||||
last_refreshed_at: Some(naive_now()),
|
||||
inbox_url: Some(person.inner.inbox()?.to_owned().into()),
|
||||
shared_inbox_url: Some(shared_inbox),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
use crate::{
|
||||
extensions::{context::lemmy_context, page_extension::PageExtension},
|
||||
fetcher::{community::get_or_fetch_and_upsert_community, user::get_or_fetch_and_upsert_user},
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
objects::{
|
||||
check_object_domain,
|
||||
check_object_for_community_or_site_ban,
|
||||
create_tombstone,
|
||||
get_object_from_apub,
|
||||
get_source_markdown_value,
|
||||
get_to_community,
|
||||
set_content_and_source,
|
||||
FromApub,
|
||||
FromApubToForm,
|
||||
|
@ -17,16 +18,20 @@ use crate::{
|
|||
use activitystreams::{
|
||||
object::{kind::PageType, ApObject, Image, Page, Tombstone},
|
||||
prelude::*,
|
||||
public,
|
||||
};
|
||||
use activitystreams_ext::Ext1;
|
||||
use anyhow::Context;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{Crud, DbPool};
|
||||
use lemmy_db_schema::source::{
|
||||
use lemmy_db_schema::{
|
||||
self,
|
||||
source::{
|
||||
community::Community,
|
||||
person::Person,
|
||||
post::{Post, PostForm},
|
||||
user::User_,
|
||||
},
|
||||
};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{
|
||||
location_info,
|
||||
request::fetch_iframely_and_pictrs_data,
|
||||
|
@ -45,7 +50,7 @@ impl ToApub for Post {
|
|||
let mut page = ApObject::new(Page::new());
|
||||
|
||||
let creator_id = self.creator_id;
|
||||
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
|
||||
let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??;
|
||||
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
|
||||
|
@ -55,28 +60,26 @@ impl ToApub for Post {
|
|||
// TODO: need to set proper context defining sensitive/commentsEnabled fields
|
||||
// https://git.asonix.dog/Aardwolf/activitystreams/issues/5
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(self.ap_id.parse::<Url>()?)
|
||||
// Use summary field to be consistent with mastodon content warning.
|
||||
// https://mastodon.xyz/@Louisa/103987265222901387.json
|
||||
.set_id(self.ap_id.to_owned().into_inner())
|
||||
.set_name(self.name.to_owned())
|
||||
// `summary` field for compatibility with lemmy v0.9.9 and older,
|
||||
// TODO: remove this after some time
|
||||
.set_summary(self.name.to_owned())
|
||||
.set_published(convert_datetime(self.published))
|
||||
.set_to(community.actor_id)
|
||||
.set_attributed_to(creator.actor_id);
|
||||
.set_many_tos(vec![community.actor_id.into_inner(), public()])
|
||||
.set_attributed_to(creator.actor_id.into_inner());
|
||||
|
||||
if let Some(body) = &self.body {
|
||||
set_content_and_source(&mut page, &body)?;
|
||||
}
|
||||
|
||||
// TODO: hacky code because we get self.url == Some("")
|
||||
// https://github.com/LemmyNet/lemmy/issues/602
|
||||
let url = self.url.as_ref().filter(|u| !u.is_empty());
|
||||
if let Some(u) = url {
|
||||
page.set_url(Url::parse(u)?);
|
||||
if let Some(url) = &self.url {
|
||||
page.set_url::<Url>(url.to_owned().into());
|
||||
}
|
||||
|
||||
if let Some(thumbnail_url) = &self.thumbnail_url {
|
||||
let mut image = Image::new();
|
||||
image.set_url(Url::parse(thumbnail_url)?);
|
||||
image.set_url::<Url>(thumbnail_url.to_owned().into());
|
||||
page.set_image(image.into_any_base()?);
|
||||
}
|
||||
|
||||
|
@ -85,15 +88,20 @@ impl ToApub for Post {
|
|||
}
|
||||
|
||||
let ext = PageExtension {
|
||||
comments_enabled: !self.locked,
|
||||
sensitive: self.nsfw,
|
||||
stickied: self.stickied,
|
||||
comments_enabled: Some(!self.locked),
|
||||
sensitive: Some(self.nsfw),
|
||||
stickied: Some(self.stickied),
|
||||
};
|
||||
Ok(Ext1::new(page, ext))
|
||||
}
|
||||
|
||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||
create_tombstone(self.deleted, &self.ap_id, self.updated, PageType::Page)
|
||||
create_tombstone(
|
||||
self.deleted,
|
||||
self.ap_id.to_owned().into(),
|
||||
self.updated,
|
||||
PageType::Page,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,8 +118,10 @@ impl FromApub for Post {
|
|||
expected_domain: Url,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<Post, LemmyError> {
|
||||
check_object_for_community_or_site_ban(page, context, request_counter).await?;
|
||||
get_object_from_apub(page, context, expected_domain, request_counter).await
|
||||
let post: Post = get_object_from_apub(page, context, expected_domain, request_counter).await?;
|
||||
check_object_for_community_or_site_ban(page, post.community_id, context, request_counter)
|
||||
.await?;
|
||||
Ok(post)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,20 +142,12 @@ impl FromApubToForm<PageExt> for PostForm {
|
|||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
|
||||
let creator = get_or_fetch_and_upsert_user(creator_actor_id, context, request_counter).await?;
|
||||
let creator =
|
||||
get_or_fetch_and_upsert_person(creator_actor_id, context, request_counter).await?;
|
||||
|
||||
let community_actor_id = page
|
||||
.inner
|
||||
.to()
|
||||
.as_ref()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
let community = get_to_community(page, context, request_counter).await?;
|
||||
|
||||
let community =
|
||||
get_or_fetch_and_upsert_community(community_actor_id, context, request_counter).await?;
|
||||
|
||||
let thumbnail_url = match &page.inner.image() {
|
||||
let thumbnail_url: Option<Url> = match &page.inner.image() {
|
||||
Some(any_image) => Image::from_any_base(
|
||||
any_image
|
||||
.to_owned()
|
||||
|
@ -157,7 +159,7 @@ impl FromApubToForm<PageExt> for PostForm {
|
|||
.url()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.map(|u| u.to_string()),
|
||||
.map(|url| url.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
let url = page
|
||||
|
@ -165,19 +167,22 @@ impl FromApubToForm<PageExt> for PostForm {
|
|||
.url()
|
||||
.map(|u| u.as_single_xsd_any_uri())
|
||||
.flatten()
|
||||
.map(|s| s.to_string());
|
||||
.map(|u| u.to_owned());
|
||||
|
||||
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
|
||||
if let Some(url) = &url {
|
||||
fetch_iframely_and_pictrs_data(context.client(), Some(url.to_owned())).await
|
||||
fetch_iframely_and_pictrs_data(context.client(), Some(url)).await
|
||||
} else {
|
||||
(None, None, None, thumbnail_url)
|
||||
};
|
||||
|
||||
let name = page
|
||||
.inner
|
||||
.summary()
|
||||
.as_ref()
|
||||
.name()
|
||||
.map(|s| s.map(|s2| s2.to_owned()))
|
||||
// The following is for compatibility with lemmy v0.9.9 and older
|
||||
// TODO: remove it after some time (along with the map above)
|
||||
.or_else(|| page.inner.summary().map(|s| s.to_owned()))
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_string()
|
||||
.context(location_info!())?
|
||||
|
@ -188,12 +193,12 @@ impl FromApubToForm<PageExt> for PostForm {
|
|||
let body_slurs_removed = body.map(|b| remove_slurs(&b));
|
||||
Ok(PostForm {
|
||||
name,
|
||||
url,
|
||||
url: url.map(|u| u.into()),
|
||||
body: body_slurs_removed,
|
||||
creator_id: creator.id,
|
||||
community_id: community.id,
|
||||
removed: None,
|
||||
locked: Some(!ext.comments_enabled),
|
||||
locked: ext.comments_enabled.map(|e| !e),
|
||||
published: page
|
||||
.inner
|
||||
.published()
|
||||
|
@ -205,12 +210,12 @@ impl FromApubToForm<PageExt> for PostForm {
|
|||
.as_ref()
|
||||
.map(|u| u.to_owned().naive_local()),
|
||||
deleted: None,
|
||||
nsfw: ext.sensitive,
|
||||
stickied: Some(ext.stickied),
|
||||
nsfw: ext.sensitive.unwrap_or(false),
|
||||
stickied: ext.stickied.or(Some(false)),
|
||||
embed_title: iframely_title,
|
||||
embed_description: iframely_description,
|
||||
embed_html: iframely_html,
|
||||
thumbnail_url: pictrs_thumbnail,
|
||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||
ap_id: Some(check_object_domain(page, expected_domain)?),
|
||||
local: false,
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
check_is_apub_id_valid,
|
||||
extensions::context::lemmy_context,
|
||||
fetcher::user::get_or_fetch_and_upsert_user,
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
objects::{
|
||||
check_object_domain,
|
||||
create_tombstone,
|
||||
|
@ -19,12 +19,12 @@ use activitystreams::{
|
|||
prelude::*,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use lemmy_api_structs::blocking;
|
||||
use lemmy_db_queries::{Crud, DbPool};
|
||||
use lemmy_db_schema::source::{
|
||||
person::Person,
|
||||
private_message::{PrivateMessage, PrivateMessageForm},
|
||||
user::User_,
|
||||
};
|
||||
use lemmy_structs::blocking;
|
||||
use lemmy_utils::{location_info, utils::convert_datetime, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use url::Url;
|
||||
|
@ -37,17 +37,17 @@ impl ToApub for PrivateMessage {
|
|||
let mut private_message = ApObject::new(Note::new());
|
||||
|
||||
let creator_id = self.creator_id;
|
||||
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
|
||||
let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??;
|
||||
|
||||
let recipient_id = self.recipient_id;
|
||||
let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
|
||||
let recipient = blocking(pool, move |conn| Person::read(conn, recipient_id)).await??;
|
||||
|
||||
private_message
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(Url::parse(&self.ap_id.to_owned())?)
|
||||
.set_id(self.ap_id.to_owned().into_inner())
|
||||
.set_published(convert_datetime(self.published))
|
||||
.set_to(recipient.actor_id)
|
||||
.set_attributed_to(creator.actor_id);
|
||||
.set_to(recipient.actor_id.into_inner())
|
||||
.set_attributed_to(creator.actor_id.into_inner());
|
||||
|
||||
set_content_and_source(&mut private_message, &self.content)?;
|
||||
|
||||
|
@ -59,7 +59,12 @@ impl ToApub for PrivateMessage {
|
|||
}
|
||||
|
||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||
create_tombstone(self.deleted, &self.ap_id, self.updated, NoteType::Note)
|
||||
create_tombstone(
|
||||
self.deleted,
|
||||
self.ap_id.to_owned().into(),
|
||||
self.updated,
|
||||
NoteType::Note,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,7 +97,8 @@ impl FromApubToForm<NoteExt> for PrivateMessageForm {
|
|||
.single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
|
||||
let creator = get_or_fetch_and_upsert_user(&creator_actor_id, context, request_counter).await?;
|
||||
let creator =
|
||||
get_or_fetch_and_upsert_person(&creator_actor_id, context, request_counter).await?;
|
||||
let recipient_actor_id = note
|
||||
.to()
|
||||
.context(location_info!())?
|
||||
|
@ -100,7 +106,7 @@ impl FromApubToForm<NoteExt> for PrivateMessageForm {
|
|||
.single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
let recipient =
|
||||
get_or_fetch_and_upsert_user(&recipient_actor_id, context, request_counter).await?;
|
||||
get_or_fetch_and_upsert_person(&recipient_actor_id, context, request_counter).await?;
|
||||
let ap_id = note.id_unchecked().context(location_info!())?.to_string();
|
||||
check_is_apub_id_valid(&Url::parse(&ap_id)?)?;
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use actix_web::*;
|
||||
use http_signature_normalization_actix::digest::middleware::VerifyDigest;
|
||||
use lemmy_apub::{
|
||||
use crate::{
|
||||
http::{
|
||||
comment::get_apub_comment,
|
||||
community::{
|
||||
|
@ -10,30 +8,34 @@ use lemmy_apub::{
|
|||
get_apub_community_outbox,
|
||||
},
|
||||
get_activity,
|
||||
person::{get_apub_person_http, get_apub_person_inbox, get_apub_person_outbox},
|
||||
post::get_apub_post,
|
||||
user::{get_apub_user_http, get_apub_user_inbox, get_apub_user_outbox},
|
||||
},
|
||||
inbox::{community_inbox::community_inbox, shared_inbox::shared_inbox, user_inbox::user_inbox},
|
||||
inbox::{
|
||||
community_inbox::community_inbox,
|
||||
person_inbox::person_inbox,
|
||||
shared_inbox::shared_inbox,
|
||||
},
|
||||
APUB_JSON_CONTENT_TYPE,
|
||||
};
|
||||
use lemmy_utils::settings::Settings;
|
||||
use actix_web::*;
|
||||
use http_signature_normalization_actix::digest::middleware::VerifyDigest;
|
||||
use lemmy_utils::settings::structs::Settings;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
static APUB_JSON_CONTENT_TYPE_LONG: &str =
|
||||
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
if Settings::get().federation.enabled {
|
||||
println!("federation enabled, host is {}", Settings::get().hostname);
|
||||
if Settings::get().federation().enabled {
|
||||
println!("federation enabled, host is {}", Settings::get().hostname());
|
||||
let digest_verifier = VerifyDigest::new(Sha256::new());
|
||||
|
||||
let header_guard_accept = guard::Any(guard::Header("Accept", APUB_JSON_CONTENT_TYPE))
|
||||
.or(guard::Header("Accept", APUB_JSON_CONTENT_TYPE_LONG));
|
||||
let header_guard_content_type =
|
||||
guard::Any(guard::Header("Content-Type", APUB_JSON_CONTENT_TYPE))
|
||||
.or(guard::Header("Content-Type", APUB_JSON_CONTENT_TYPE_LONG))
|
||||
// TODO: compatibility with previous lemmy versions, remove this later
|
||||
.or(guard::Header("Content-Type", "application/json"));
|
||||
.or(guard::Header("Content-Type", APUB_JSON_CONTENT_TYPE_LONG));
|
||||
|
||||
cfg
|
||||
.service(
|
||||
|
@ -55,9 +57,12 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||
"/c/{community_name}/inbox",
|
||||
web::get().to(get_apub_community_inbox),
|
||||
)
|
||||
.route("/u/{user_name}", web::get().to(get_apub_user_http))
|
||||
.route("/u/{user_name}/outbox", web::get().to(get_apub_user_outbox))
|
||||
.route("/u/{user_name}/inbox", web::get().to(get_apub_user_inbox))
|
||||
.route("/u/{user_name}", web::get().to(get_apub_person_http))
|
||||
.route(
|
||||
"/u/{user_name}/outbox",
|
||||
web::get().to(get_apub_person_outbox),
|
||||
)
|
||||
.route("/u/{user_name}/inbox", web::get().to(get_apub_person_inbox))
|
||||
.route("/post/{post_id}", web::get().to(get_apub_post))
|
||||
.route("/comment/{comment_id}", web::get().to(get_apub_comment))
|
||||
.route("/activities/{type_}/{id}", web::get().to(get_activity)),
|
||||
|
@ -68,7 +73,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||
.wrap(digest_verifier)
|
||||
.guard(header_guard_content_type)
|
||||
.route("/c/{community_name}/inbox", web::post().to(community_inbox))
|
||||
.route("/u/{user_name}/inbox", web::post().to(user_inbox))
|
||||
.route("/u/{user_name}/inbox", web::post().to(person_inbox))
|
||||
.route("/inbox", web::post().to(shared_inbox)),
|
||||
);
|
||||
}
|
|
@ -6,6 +6,7 @@ edition = "2018"
|
|||
[lib]
|
||||
name = "lemmy_db_queries"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
lemmy_utils = { path = "../utils" }
|
||||
|
@ -13,13 +14,16 @@ lemmy_db_schema = { path = "../db_schema" }
|
|||
diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","serde_json"] }
|
||||
diesel_migrations = "1.4.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
serde = { version = "1.0.118", features = ["derive"] }
|
||||
serde_json = { version = "1.0.60", features = ["preserve_order"] }
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
serde_json = { version = "1.0.61", features = ["preserve_order"] }
|
||||
strum = "0.20.0"
|
||||
strum_macros = "0.20.1"
|
||||
log = "0.4.11"
|
||||
sha2 = "0.9.2"
|
||||
url = { version = "2.2.0", features = ["serde"] }
|
||||
log = "0.4.14"
|
||||
sha2 = "0.9.3"
|
||||
url = { version = "2.2.1", features = ["serde"] }
|
||||
lazy_static = "1.4.0"
|
||||
regex = "1.4.2"
|
||||
regex = "1.4.3"
|
||||
bcrypt = "0.9.0"
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.5.1"
|
|
@ -1,12 +1,12 @@
|
|||
use diesel::{result::Error, *};
|
||||
use lemmy_db_schema::schema::comment_aggregates;
|
||||
use lemmy_db_schema::{schema::comment_aggregates, CommentId};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
|
||||
#[table_name = "comment_aggregates"]
|
||||
pub struct CommentAggregates {
|
||||
pub id: i32,
|
||||
pub comment_id: i32,
|
||||
pub comment_id: CommentId,
|
||||
pub score: i64,
|
||||
pub upvotes: i64,
|
||||
pub downvotes: i64,
|
||||
|
@ -14,7 +14,7 @@ pub struct CommentAggregates {
|
|||
}
|
||||
|
||||
impl CommentAggregates {
|
||||
pub fn read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
|
||||
pub fn read(conn: &PgConnection, comment_id: CommentId) -> Result<Self, Error> {
|
||||
comment_aggregates::table
|
||||
.filter(comment_aggregates::comment_id.eq(comment_id))
|
||||
.first::<Self>(conn)
|
||||
|
@ -28,84 +28,67 @@ mod tests {
|
|||
establish_unpooled_connection,
|
||||
Crud,
|
||||
Likeable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_schema::source::{
|
||||
comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
|
||||
community::{Community, CommunityForm},
|
||||
person::{Person, PersonForm},
|
||||
post::{Post, PostForm},
|
||||
user::{UserForm, User_},
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let new_user = UserForm {
|
||||
let new_person = PersonForm {
|
||||
name: "thommy_comment_agg".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
||||
|
||||
let another_user = UserForm {
|
||||
let another_person = PersonForm {
|
||||
name: "jerry_comment_agg".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let another_inserted_user = User_::create(&conn, &another_user).unwrap();
|
||||
let another_inserted_person = Person::create(&conn, &another_person).unwrap();
|
||||
|
||||
let new_community = CommunityForm {
|
||||
name: "TIL_comment_agg".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
title: "nada".to_owned(),
|
||||
description: None,
|
||||
category_id: 1,
|
||||
nsfw: false,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -118,6 +101,9 @@ mod tests {
|
|||
published: None,
|
||||
icon: None,
|
||||
banner: None,
|
||||
followers_url: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||
|
@ -126,7 +112,7 @@ mod tests {
|
|||
name: "A test post".into(),
|
||||
url: None,
|
||||
body: None,
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
community_id: inserted_community.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -147,7 +133,7 @@ mod tests {
|
|||
|
||||
let comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -163,7 +149,7 @@ mod tests {
|
|||
|
||||
let child_comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -180,7 +166,7 @@ mod tests {
|
|||
let comment_like = CommentLikeForm {
|
||||
comment_id: inserted_comment.id,
|
||||
post_id: inserted_post.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
score: 1,
|
||||
};
|
||||
|
||||
|
@ -192,11 +178,11 @@ mod tests {
|
|||
assert_eq!(1, comment_aggs_before_delete.upvotes);
|
||||
assert_eq!(0, comment_aggs_before_delete.downvotes);
|
||||
|
||||
// Add a post dislike from the other user
|
||||
// Add a post dislike from the other person
|
||||
let comment_dislike = CommentLikeForm {
|
||||
comment_id: inserted_comment.id,
|
||||
post_id: inserted_post.id,
|
||||
user_id: another_inserted_user.id,
|
||||
person_id: another_inserted_person.id,
|
||||
score: -1,
|
||||
};
|
||||
|
||||
|
@ -209,7 +195,7 @@ mod tests {
|
|||
assert_eq!(1, comment_aggs_after_dislike.downvotes);
|
||||
|
||||
// Remove the first comment like
|
||||
CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
|
||||
CommentLike::remove(&conn, inserted_person.id, inserted_comment.id).unwrap();
|
||||
let after_like_remove = CommentAggregates::read(&conn, inserted_comment.id).unwrap();
|
||||
assert_eq!(-1, after_like_remove.score);
|
||||
assert_eq!(0, after_like_remove.upvotes);
|
||||
|
@ -223,8 +209,8 @@ mod tests {
|
|||
assert!(after_delete.is_err());
|
||||
|
||||
// This should delete all the associated rows, and fire triggers
|
||||
User_::delete(&conn, another_inserted_user.id).unwrap();
|
||||
let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
|
||||
assert_eq!(1, user_num_deleted);
|
||||
Person::delete(&conn, another_inserted_person.id).unwrap();
|
||||
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
|
||||
assert_eq!(1, person_num_deleted);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
use diesel::{result::Error, *};
|
||||
use lemmy_db_schema::schema::community_aggregates;
|
||||
use lemmy_db_schema::{schema::community_aggregates, CommunityId};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
|
||||
#[table_name = "community_aggregates"]
|
||||
pub struct CommunityAggregates {
|
||||
pub id: i32,
|
||||
pub community_id: i32,
|
||||
pub community_id: CommunityId,
|
||||
pub subscribers: i64,
|
||||
pub posts: i64,
|
||||
pub comments: i64,
|
||||
pub published: chrono::NaiveDateTime,
|
||||
pub users_active_day: i64,
|
||||
pub users_active_week: i64,
|
||||
pub users_active_month: i64,
|
||||
pub users_active_half_year: i64,
|
||||
}
|
||||
|
||||
impl CommunityAggregates {
|
||||
pub fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
|
||||
pub fn read(conn: &PgConnection, community_id: CommunityId) -> Result<Self, Error> {
|
||||
community_aggregates::table
|
||||
.filter(community_aggregates::community_id.eq(community_id))
|
||||
.first::<Self>(conn)
|
||||
|
@ -28,84 +32,67 @@ mod tests {
|
|||
establish_unpooled_connection,
|
||||
Crud,
|
||||
Followable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_schema::source::{
|
||||
comment::{Comment, CommentForm},
|
||||
community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm},
|
||||
person::{Person, PersonForm},
|
||||
post::{Post, PostForm},
|
||||
user::{UserForm, User_},
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let new_user = UserForm {
|
||||
let new_person = PersonForm {
|
||||
name: "thommy_community_agg".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
||||
|
||||
let another_user = UserForm {
|
||||
let another_person = PersonForm {
|
||||
name: "jerry_community_agg".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let another_inserted_user = User_::create(&conn, &another_user).unwrap();
|
||||
let another_inserted_person = Person::create(&conn, &another_person).unwrap();
|
||||
|
||||
let new_community = CommunityForm {
|
||||
name: "TIL_community_agg".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
title: "nada".to_owned(),
|
||||
description: None,
|
||||
category_id: 1,
|
||||
nsfw: false,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -118,16 +105,18 @@ mod tests {
|
|||
published: None,
|
||||
icon: None,
|
||||
banner: None,
|
||||
followers_url: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||
|
||||
let another_community = CommunityForm {
|
||||
name: "TIL_community_agg_2".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
title: "nada".to_owned(),
|
||||
description: None,
|
||||
category_id: 1,
|
||||
nsfw: false,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -140,29 +129,32 @@ mod tests {
|
|||
published: None,
|
||||
icon: None,
|
||||
banner: None,
|
||||
followers_url: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let another_inserted_community = Community::create(&conn, &another_community).unwrap();
|
||||
|
||||
let first_user_follow = CommunityFollowerForm {
|
||||
let first_person_follow = CommunityFollowerForm {
|
||||
community_id: inserted_community.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
CommunityFollower::follow(&conn, &first_user_follow).unwrap();
|
||||
CommunityFollower::follow(&conn, &first_person_follow).unwrap();
|
||||
|
||||
let second_user_follow = CommunityFollowerForm {
|
||||
let second_person_follow = CommunityFollowerForm {
|
||||
community_id: inserted_community.id,
|
||||
user_id: another_inserted_user.id,
|
||||
person_id: another_inserted_person.id,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
CommunityFollower::follow(&conn, &second_user_follow).unwrap();
|
||||
CommunityFollower::follow(&conn, &second_person_follow).unwrap();
|
||||
|
||||
let another_community_follow = CommunityFollowerForm {
|
||||
community_id: another_inserted_community.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
|
@ -172,7 +164,7 @@ mod tests {
|
|||
name: "A test post".into(),
|
||||
url: None,
|
||||
body: None,
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
community_id: inserted_community.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -193,7 +185,7 @@ mod tests {
|
|||
|
||||
let comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -209,7 +201,7 @@ mod tests {
|
|||
|
||||
let child_comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -238,12 +230,12 @@ mod tests {
|
|||
assert_eq!(0, another_community_aggs.comments);
|
||||
|
||||
// Unfollow test
|
||||
CommunityFollower::unfollow(&conn, &second_user_follow).unwrap();
|
||||
CommunityFollower::unfollow(&conn, &second_person_follow).unwrap();
|
||||
let after_unfollow = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
|
||||
assert_eq!(1, after_unfollow.subscribers);
|
||||
|
||||
// Follow again just for the later tests
|
||||
CommunityFollower::follow(&conn, &second_user_follow).unwrap();
|
||||
CommunityFollower::follow(&conn, &second_person_follow).unwrap();
|
||||
let after_follow_again = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
|
||||
assert_eq!(2, after_follow_again.subscribers);
|
||||
|
||||
|
@ -253,14 +245,14 @@ mod tests {
|
|||
assert_eq!(0, after_parent_post_delete.comments);
|
||||
assert_eq!(0, after_parent_post_delete.posts);
|
||||
|
||||
// Remove the 2nd user
|
||||
User_::delete(&conn, another_inserted_user.id).unwrap();
|
||||
let after_user_delete = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
|
||||
assert_eq!(1, after_user_delete.subscribers);
|
||||
// Remove the 2nd person
|
||||
Person::delete(&conn, another_inserted_person.id).unwrap();
|
||||
let after_person_delete = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
|
||||
assert_eq!(1, after_person_delete.subscribers);
|
||||
|
||||
// This should delete all the associated rows, and fire triggers
|
||||
let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
|
||||
assert_eq!(1, user_num_deleted);
|
||||
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
|
||||
assert_eq!(1, person_num_deleted);
|
||||
|
||||
// Should be none found, since the creator was deleted
|
||||
let after_delete = CommunityAggregates::read(&conn, inserted_community.id);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
pub mod comment_aggregates;
|
||||
pub mod community_aggregates;
|
||||
pub mod person_aggregates;
|
||||
pub mod post_aggregates;
|
||||
pub mod site_aggregates;
|
||||
pub mod user_aggregates;
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
use diesel::{result::Error, *};
|
||||
use lemmy_db_schema::schema::user_aggregates;
|
||||
use lemmy_db_schema::{schema::person_aggregates, PersonId};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
|
||||
#[table_name = "user_aggregates"]
|
||||
pub struct UserAggregates {
|
||||
#[table_name = "person_aggregates"]
|
||||
pub struct PersonAggregates {
|
||||
pub id: i32,
|
||||
pub user_id: i32,
|
||||
pub person_id: PersonId,
|
||||
pub post_count: i64,
|
||||
pub post_score: i64,
|
||||
pub comment_count: i64,
|
||||
pub comment_score: i64,
|
||||
}
|
||||
|
||||
impl UserAggregates {
|
||||
pub fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
|
||||
user_aggregates::table
|
||||
.filter(user_aggregates::user_id.eq(user_id))
|
||||
impl PersonAggregates {
|
||||
pub fn read(conn: &PgConnection, person_id: PersonId) -> Result<Self, Error> {
|
||||
person_aggregates::table
|
||||
.filter(person_aggregates::person_id.eq(person_id))
|
||||
.first::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
@ -24,88 +24,71 @@ impl UserAggregates {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
aggregates::user_aggregates::UserAggregates,
|
||||
aggregates::person_aggregates::PersonAggregates,
|
||||
establish_unpooled_connection,
|
||||
Crud,
|
||||
Likeable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_schema::source::{
|
||||
comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
|
||||
community::{Community, CommunityForm},
|
||||
person::{Person, PersonForm},
|
||||
post::{Post, PostForm, PostLike, PostLikeForm},
|
||||
user::{UserForm, User_},
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let new_user = UserForm {
|
||||
let new_person = PersonForm {
|
||||
name: "thommy_user_agg".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
||||
|
||||
let another_user = UserForm {
|
||||
let another_person = PersonForm {
|
||||
name: "jerry_user_agg".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let another_inserted_user = User_::create(&conn, &another_user).unwrap();
|
||||
let another_inserted_person = Person::create(&conn, &another_person).unwrap();
|
||||
|
||||
let new_community = CommunityForm {
|
||||
name: "TIL_site_agg".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
title: "nada".to_owned(),
|
||||
description: None,
|
||||
category_id: 1,
|
||||
nsfw: false,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -118,6 +101,9 @@ mod tests {
|
|||
published: None,
|
||||
icon: None,
|
||||
banner: None,
|
||||
followers_url: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||
|
@ -126,7 +112,7 @@ mod tests {
|
|||
name: "A test post".into(),
|
||||
url: None,
|
||||
body: None,
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
community_id: inserted_community.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -147,7 +133,7 @@ mod tests {
|
|||
|
||||
let post_like = PostLikeForm {
|
||||
post_id: inserted_post.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
score: 1,
|
||||
};
|
||||
|
||||
|
@ -155,7 +141,7 @@ mod tests {
|
|||
|
||||
let comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -171,7 +157,7 @@ mod tests {
|
|||
|
||||
let mut comment_like = CommentLikeForm {
|
||||
comment_id: inserted_comment.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
score: 1,
|
||||
};
|
||||
|
@ -180,7 +166,7 @@ mod tests {
|
|||
|
||||
let mut child_comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -196,28 +182,29 @@ mod tests {
|
|||
|
||||
let child_comment_like = CommentLikeForm {
|
||||
comment_id: inserted_child_comment.id,
|
||||
user_id: another_inserted_user.id,
|
||||
person_id: another_inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
score: 1,
|
||||
};
|
||||
|
||||
let _inserted_child_comment_like = CommentLike::like(&conn, &child_comment_like).unwrap();
|
||||
|
||||
let user_aggregates_before_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
|
||||
let person_aggregates_before_delete =
|
||||
PersonAggregates::read(&conn, inserted_person.id).unwrap();
|
||||
|
||||
assert_eq!(1, user_aggregates_before_delete.post_count);
|
||||
assert_eq!(1, user_aggregates_before_delete.post_score);
|
||||
assert_eq!(2, user_aggregates_before_delete.comment_count);
|
||||
assert_eq!(2, user_aggregates_before_delete.comment_score);
|
||||
assert_eq!(1, person_aggregates_before_delete.post_count);
|
||||
assert_eq!(1, person_aggregates_before_delete.post_score);
|
||||
assert_eq!(2, person_aggregates_before_delete.comment_count);
|
||||
assert_eq!(2, person_aggregates_before_delete.comment_score);
|
||||
|
||||
// Remove a post like
|
||||
PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
|
||||
let after_post_like_remove = UserAggregates::read(&conn, inserted_user.id).unwrap();
|
||||
PostLike::remove(&conn, inserted_person.id, inserted_post.id).unwrap();
|
||||
let after_post_like_remove = PersonAggregates::read(&conn, inserted_person.id).unwrap();
|
||||
assert_eq!(0, after_post_like_remove.post_score);
|
||||
|
||||
// Remove a parent comment (the scores should also be removed)
|
||||
Comment::delete(&conn, inserted_comment.id).unwrap();
|
||||
let after_parent_comment_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
|
||||
let after_parent_comment_delete = PersonAggregates::read(&conn, inserted_person.id).unwrap();
|
||||
assert_eq!(0, after_parent_comment_delete.comment_count);
|
||||
assert_eq!(0, after_parent_comment_delete.comment_score);
|
||||
|
||||
|
@ -227,24 +214,24 @@ mod tests {
|
|||
Comment::create(&conn, &child_comment_form).unwrap();
|
||||
comment_like.comment_id = new_parent_comment.id;
|
||||
CommentLike::like(&conn, &comment_like).unwrap();
|
||||
let after_comment_add = UserAggregates::read(&conn, inserted_user.id).unwrap();
|
||||
let after_comment_add = PersonAggregates::read(&conn, inserted_person.id).unwrap();
|
||||
assert_eq!(2, after_comment_add.comment_count);
|
||||
assert_eq!(1, after_comment_add.comment_score);
|
||||
|
||||
Post::delete(&conn, inserted_post.id).unwrap();
|
||||
let after_post_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
|
||||
let after_post_delete = PersonAggregates::read(&conn, inserted_person.id).unwrap();
|
||||
assert_eq!(0, after_post_delete.comment_score);
|
||||
assert_eq!(0, after_post_delete.comment_count);
|
||||
assert_eq!(0, after_post_delete.post_score);
|
||||
assert_eq!(0, after_post_delete.post_count);
|
||||
|
||||
// This should delete all the associated rows, and fire triggers
|
||||
let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
|
||||
assert_eq!(1, user_num_deleted);
|
||||
User_::delete(&conn, another_inserted_user.id).unwrap();
|
||||
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
|
||||
assert_eq!(1, person_num_deleted);
|
||||
Person::delete(&conn, another_inserted_person.id).unwrap();
|
||||
|
||||
// Should be none found
|
||||
let after_delete = UserAggregates::read(&conn, inserted_user.id);
|
||||
let after_delete = PersonAggregates::read(&conn, inserted_person.id);
|
||||
assert!(after_delete.is_err());
|
||||
}
|
||||
}
|
|
@ -1,23 +1,24 @@
|
|||
use diesel::{result::Error, *};
|
||||
use lemmy_db_schema::schema::post_aggregates;
|
||||
use lemmy_db_schema::{schema::post_aggregates, PostId};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
|
||||
#[table_name = "post_aggregates"]
|
||||
pub struct PostAggregates {
|
||||
pub id: i32,
|
||||
pub post_id: i32,
|
||||
pub post_id: PostId,
|
||||
pub comments: i64,
|
||||
pub score: i64,
|
||||
pub upvotes: i64,
|
||||
pub downvotes: i64,
|
||||
pub stickied: bool,
|
||||
pub published: chrono::NaiveDateTime,
|
||||
pub newest_comment_time_necro: chrono::NaiveDateTime, // A newest comment time, limited to 2 days, to prevent necrobumping
|
||||
pub newest_comment_time: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
impl PostAggregates {
|
||||
pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
|
||||
pub fn read(conn: &PgConnection, post_id: PostId) -> Result<Self, Error> {
|
||||
post_aggregates::table
|
||||
.filter(post_aggregates::post_id.eq(post_id))
|
||||
.first::<Self>(conn)
|
||||
|
@ -31,84 +32,67 @@ mod tests {
|
|||
establish_unpooled_connection,
|
||||
Crud,
|
||||
Likeable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_schema::source::{
|
||||
comment::{Comment, CommentForm},
|
||||
community::{Community, CommunityForm},
|
||||
person::{Person, PersonForm},
|
||||
post::{Post, PostForm, PostLike, PostLikeForm},
|
||||
user::{UserForm, User_},
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let new_user = UserForm {
|
||||
let new_person = PersonForm {
|
||||
name: "thommy_community_agg".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
||||
|
||||
let another_user = UserForm {
|
||||
let another_person = PersonForm {
|
||||
name: "jerry_community_agg".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let another_inserted_user = User_::create(&conn, &another_user).unwrap();
|
||||
let another_inserted_person = Person::create(&conn, &another_person).unwrap();
|
||||
|
||||
let new_community = CommunityForm {
|
||||
name: "TIL_community_agg".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
title: "nada".to_owned(),
|
||||
description: None,
|
||||
category_id: 1,
|
||||
nsfw: false,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -121,6 +105,9 @@ mod tests {
|
|||
published: None,
|
||||
icon: None,
|
||||
banner: None,
|
||||
followers_url: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||
|
@ -129,7 +116,7 @@ mod tests {
|
|||
name: "A test post".into(),
|
||||
url: None,
|
||||
body: None,
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
community_id: inserted_community.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -150,7 +137,7 @@ mod tests {
|
|||
|
||||
let comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -166,7 +153,7 @@ mod tests {
|
|||
|
||||
let child_comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -182,7 +169,7 @@ mod tests {
|
|||
|
||||
let post_like = PostLikeForm {
|
||||
post_id: inserted_post.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
score: 1,
|
||||
};
|
||||
|
||||
|
@ -195,10 +182,10 @@ mod tests {
|
|||
assert_eq!(1, post_aggs_before_delete.upvotes);
|
||||
assert_eq!(0, post_aggs_before_delete.downvotes);
|
||||
|
||||
// Add a post dislike from the other user
|
||||
// Add a post dislike from the other person
|
||||
let post_dislike = PostLikeForm {
|
||||
post_id: inserted_post.id,
|
||||
user_id: another_inserted_user.id,
|
||||
person_id: another_inserted_person.id,
|
||||
score: -1,
|
||||
};
|
||||
|
||||
|
@ -220,7 +207,7 @@ mod tests {
|
|||
assert_eq!(1, after_comment_delete.downvotes);
|
||||
|
||||
// Remove the first post like
|
||||
PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
|
||||
PostLike::remove(&conn, inserted_person.id, inserted_post.id).unwrap();
|
||||
let after_like_remove = PostAggregates::read(&conn, inserted_post.id).unwrap();
|
||||
assert_eq!(0, after_like_remove.comments);
|
||||
assert_eq!(-1, after_like_remove.score);
|
||||
|
@ -228,9 +215,9 @@ mod tests {
|
|||
assert_eq!(1, after_like_remove.downvotes);
|
||||
|
||||
// This should delete all the associated rows, and fire triggers
|
||||
User_::delete(&conn, another_inserted_user.id).unwrap();
|
||||
let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
|
||||
assert_eq!(1, user_num_deleted);
|
||||
Person::delete(&conn, another_inserted_person.id).unwrap();
|
||||
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
|
||||
assert_eq!(1, person_num_deleted);
|
||||
|
||||
// Should be none found, since the creator was deleted
|
||||
let after_delete = PostAggregates::read(&conn, inserted_post.id);
|
||||
|
|
|
@ -11,6 +11,10 @@ pub struct SiteAggregates {
|
|||
pub posts: i64,
|
||||
pub comments: i64,
|
||||
pub communities: i64,
|
||||
pub users_active_day: i64,
|
||||
pub users_active_week: i64,
|
||||
pub users_active_month: i64,
|
||||
pub users_active_half_year: i64,
|
||||
}
|
||||
|
||||
impl SiteAggregates {
|
||||
|
@ -21,60 +25,48 @@ impl SiteAggregates {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
aggregates::site_aggregates::SiteAggregates,
|
||||
establish_unpooled_connection,
|
||||
Crud,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use crate::{aggregates::site_aggregates::SiteAggregates, establish_unpooled_connection, Crud};
|
||||
use lemmy_db_schema::source::{
|
||||
comment::{Comment, CommentForm},
|
||||
community::{Community, CommunityForm},
|
||||
person::{Person, PersonForm},
|
||||
post::{Post, PostForm},
|
||||
site::{Site, SiteForm},
|
||||
user::{UserForm, User_},
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let new_user = UserForm {
|
||||
let new_person = PersonForm {
|
||||
name: "thommy_site_agg".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
||||
|
||||
let site_form = SiteForm {
|
||||
name: "test_site".into(),
|
||||
description: None,
|
||||
icon: None,
|
||||
banner: None,
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
enable_downvotes: true,
|
||||
open_registration: true,
|
||||
enable_nsfw: true,
|
||||
|
@ -85,10 +77,9 @@ mod tests {
|
|||
|
||||
let new_community = CommunityForm {
|
||||
name: "TIL_site_agg".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
title: "nada".to_owned(),
|
||||
description: None,
|
||||
category_id: 1,
|
||||
nsfw: false,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -101,6 +92,9 @@ mod tests {
|
|||
published: None,
|
||||
icon: None,
|
||||
banner: None,
|
||||
followers_url: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||
|
@ -109,7 +103,7 @@ mod tests {
|
|||
name: "A test post".into(),
|
||||
url: None,
|
||||
body: None,
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
community_id: inserted_community.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -132,7 +126,7 @@ mod tests {
|
|||
|
||||
let comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -149,7 +143,7 @@ mod tests {
|
|||
|
||||
let child_comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -177,8 +171,8 @@ mod tests {
|
|||
assert_eq!(0, site_aggregates_after_post_delete.comments);
|
||||
|
||||
// This shouuld delete all the associated rows, and fire triggers
|
||||
let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
|
||||
assert_eq!(1, user_num_deleted);
|
||||
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
|
||||
assert_eq!(1, person_num_deleted);
|
||||
|
||||
let after_delete = SiteAggregates::read(&conn);
|
||||
assert!(after_delete.is_err());
|
||||
|
|
|
@ -9,27 +9,33 @@ extern crate lazy_static;
|
|||
#[macro_use]
|
||||
extern crate diesel_migrations;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate serial_test;
|
||||
|
||||
use diesel::{result::Error, *};
|
||||
use lemmy_db_schema::{CommunityId, DbUrl, PersonId};
|
||||
use lemmy_utils::ApiError;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{env, env::VarError};
|
||||
use url::Url;
|
||||
|
||||
pub mod aggregates;
|
||||
pub mod source;
|
||||
|
||||
pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
|
||||
|
||||
pub trait Crud<T> {
|
||||
fn create(conn: &PgConnection, form: &T) -> Result<Self, Error>
|
||||
pub trait Crud<Form, IdType> {
|
||||
fn create(conn: &PgConnection, form: &Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn read(conn: &PgConnection, id: i32) -> Result<Self, Error>
|
||||
fn read(conn: &PgConnection, id: IdType) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn update(conn: &PgConnection, id: i32, form: &T) -> Result<Self, Error>
|
||||
fn update(conn: &PgConnection, id: IdType, form: &Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn delete(_conn: &PgConnection, _id: i32) -> Result<usize, Error>
|
||||
fn delete(_conn: &PgConnection, _id: IdType) -> Result<usize, Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -37,81 +43,85 @@ pub trait Crud<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait Followable<T> {
|
||||
fn follow(conn: &PgConnection, form: &T) -> Result<Self, Error>
|
||||
pub trait Followable<Form> {
|
||||
fn follow(conn: &PgConnection, form: &Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn follow_accepted(conn: &PgConnection, community_id: i32, user_id: i32) -> Result<Self, Error>
|
||||
fn follow_accepted(
|
||||
conn: &PgConnection,
|
||||
community_id: CommunityId,
|
||||
person_id: PersonId,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn unfollow(conn: &PgConnection, form: &T) -> Result<usize, Error>
|
||||
fn unfollow(conn: &PgConnection, form: &Form) -> Result<usize, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn has_local_followers(conn: &PgConnection, community_id: i32) -> Result<bool, Error>;
|
||||
fn has_local_followers(conn: &PgConnection, community_id: CommunityId) -> Result<bool, Error>;
|
||||
}
|
||||
|
||||
pub trait Joinable<T> {
|
||||
fn join(conn: &PgConnection, form: &T) -> Result<Self, Error>
|
||||
pub trait Joinable<Form> {
|
||||
fn join(conn: &PgConnection, form: &Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn leave(conn: &PgConnection, form: &T) -> Result<usize, Error>
|
||||
fn leave(conn: &PgConnection, form: &Form) -> Result<usize, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait Likeable<T> {
|
||||
fn like(conn: &PgConnection, form: &T) -> Result<Self, Error>
|
||||
pub trait Likeable<Form, IdType> {
|
||||
fn like(conn: &PgConnection, form: &Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn remove(conn: &PgConnection, user_id: i32, item_id: i32) -> Result<usize, Error>
|
||||
fn remove(conn: &PgConnection, person_id: PersonId, item_id: IdType) -> Result<usize, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait Bannable<T> {
|
||||
fn ban(conn: &PgConnection, form: &T) -> Result<Self, Error>
|
||||
pub trait Bannable<Form> {
|
||||
fn ban(conn: &PgConnection, form: &Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn unban(conn: &PgConnection, form: &T) -> Result<usize, Error>
|
||||
fn unban(conn: &PgConnection, form: &Form) -> Result<usize, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait Saveable<T> {
|
||||
fn save(conn: &PgConnection, form: &T) -> Result<Self, Error>
|
||||
pub trait Saveable<Form> {
|
||||
fn save(conn: &PgConnection, form: &Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn unsave(conn: &PgConnection, form: &T) -> Result<usize, Error>
|
||||
fn unsave(conn: &PgConnection, form: &Form) -> Result<usize, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait Readable<T> {
|
||||
fn mark_as_read(conn: &PgConnection, form: &T) -> Result<Self, Error>
|
||||
pub trait Readable<Form> {
|
||||
fn mark_as_read(conn: &PgConnection, form: &Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn mark_as_unread(conn: &PgConnection, form: &T) -> Result<usize, Error>
|
||||
fn mark_as_unread(conn: &PgConnection, form: &Form) -> Result<usize, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait Reportable<T> {
|
||||
fn report(conn: &PgConnection, form: &T) -> Result<Self, Error>
|
||||
pub trait Reportable<Form> {
|
||||
fn report(conn: &PgConnection, form: &Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn resolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result<usize, Error>
|
||||
fn resolve(conn: &PgConnection, report_id: i32, resolver_id: PersonId) -> Result<usize, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn unresolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result<usize, Error>
|
||||
fn unresolve(conn: &PgConnection, report_id: i32, resolver_id: PersonId) -> Result<usize, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait ApubObject<T> {
|
||||
fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error>
|
||||
pub trait ApubObject<Form> {
|
||||
fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
fn upsert(conn: &PgConnection, user_form: &T) -> Result<Self, Error>
|
||||
fn upsert(conn: &PgConnection, user_form: &Form) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -163,6 +173,8 @@ pub enum SortType {
|
|||
TopMonth,
|
||||
TopYear,
|
||||
TopAll,
|
||||
MostComments,
|
||||
NewComments,
|
||||
}
|
||||
|
||||
#[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone)]
|
||||
|
@ -213,6 +225,20 @@ pub fn diesel_option_overwrite(opt: &Option<String>) -> Option<Option<String>> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn diesel_option_overwrite_to_url(
|
||||
opt: &Option<String>,
|
||||
) -> Result<Option<Option<DbUrl>>, ApiError> {
|
||||
match opt.as_ref().map(|s| s.as_str()) {
|
||||
// An empty string is an erase
|
||||
Some("") => Ok(Some(None)),
|
||||
Some(str_url) => match Url::parse(str_url) {
|
||||
Ok(url) => Ok(Some(Some(url.into()))),
|
||||
Err(_) => Err(ApiError::err("invalid_url")),
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
embed_migrations!();
|
||||
|
||||
pub fn establish_unpooled_connection() -> PgConnection {
|
||||
|
@ -225,13 +251,14 @@ pub fn establish_unpooled_connection() -> PgConnection {
|
|||
};
|
||||
let conn =
|
||||
PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
|
||||
embedded_migrations::run(&conn).unwrap();
|
||||
embedded_migrations::run(&conn).expect("load migrations");
|
||||
conn
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref EMAIL_REGEX: Regex =
|
||||
Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
|
||||
Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
|
||||
.expect("compile email regex");
|
||||
}
|
||||
|
||||
pub mod functions {
|
||||
|
@ -244,7 +271,7 @@ pub mod functions {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::fuzzy_search;
|
||||
use super::{fuzzy_search, *};
|
||||
use crate::is_email_regex;
|
||||
|
||||
#[test]
|
||||
|
@ -258,4 +285,32 @@ mod tests {
|
|||
assert!(is_email_regex("gush@gmail.com"));
|
||||
assert!(!is_email_regex("nada_neutho"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_diesel_option_overwrite() {
|
||||
assert_eq!(diesel_option_overwrite(&None), None);
|
||||
assert_eq!(diesel_option_overwrite(&Some("".to_string())), Some(None));
|
||||
assert_eq!(
|
||||
diesel_option_overwrite(&Some("test".to_string())),
|
||||
Some(Some("test".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_diesel_option_overwrite_to_url() {
|
||||
assert!(matches!(diesel_option_overwrite_to_url(&None), Ok(None)));
|
||||
assert!(matches!(
|
||||
diesel_option_overwrite_to_url(&Some("".to_string())),
|
||||
Ok(Some(None))
|
||||
));
|
||||
assert!(matches!(
|
||||
diesel_option_overwrite_to_url(&Some("invalid_url".to_string())),
|
||||
Err(_)
|
||||
));
|
||||
let example_url = "https://example.com";
|
||||
assert!(matches!(
|
||||
diesel_option_overwrite_to_url(&Some(example_url.to_string())),
|
||||
Ok(Some(Some(url))) if url == Url::parse(&example_url).unwrap().into()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use crate::Crud;
|
||||
use diesel::{dsl::*, result::Error, *};
|
||||
use lemmy_db_schema::source::activity::*;
|
||||
use diesel::{dsl::*, result::Error, sql_types::Text, *};
|
||||
use lemmy_db_schema::{source::activity::*, DbUrl};
|
||||
use log::debug;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
io::{Error as IoError, ErrorKind},
|
||||
};
|
||||
|
||||
impl Crud<ActivityForm> for Activity {
|
||||
impl Crud<ActivityForm, i32> for Activity {
|
||||
fn read(conn: &PgConnection, activity_id: i32) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::activity::dsl::*;
|
||||
activity.find(activity_id).first::<Self>(conn)
|
||||
|
@ -40,20 +41,28 @@ impl Crud<ActivityForm> for Activity {
|
|||
pub trait Activity_ {
|
||||
fn insert<T>(
|
||||
conn: &PgConnection,
|
||||
ap_id: String,
|
||||
ap_id: DbUrl,
|
||||
data: &T,
|
||||
local: bool,
|
||||
sensitive: bool,
|
||||
) -> Result<Activity, IoError>
|
||||
where
|
||||
T: Serialize + Debug;
|
||||
fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Activity, Error>;
|
||||
|
||||
fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Activity, Error>;
|
||||
fn delete_olds(conn: &PgConnection) -> Result<usize, Error>;
|
||||
|
||||
/// Returns up to 20 activities of type `Announce/Create/Page` from the community
|
||||
fn read_community_outbox(
|
||||
conn: &PgConnection,
|
||||
community_actor_id: &DbUrl,
|
||||
) -> Result<Vec<Value>, Error>;
|
||||
}
|
||||
|
||||
impl Activity_ for Activity {
|
||||
fn insert<T>(
|
||||
conn: &PgConnection,
|
||||
ap_id: String,
|
||||
ap_id: DbUrl,
|
||||
data: &T,
|
||||
local: bool,
|
||||
sensitive: bool,
|
||||
|
@ -79,62 +88,80 @@ impl Activity_ for Activity {
|
|||
}
|
||||
}
|
||||
|
||||
fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Activity, Error> {
|
||||
fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Activity, Error> {
|
||||
use lemmy_db_schema::schema::activity::dsl::*;
|
||||
activity.filter(ap_id.eq(object_id)).first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn delete_olds(conn: &PgConnection) -> Result<usize, Error> {
|
||||
use lemmy_db_schema::schema::activity::dsl::*;
|
||||
diesel::delete(activity.filter(published.lt(now - 6.months()))).execute(conn)
|
||||
}
|
||||
|
||||
fn read_community_outbox(
|
||||
conn: &PgConnection,
|
||||
community_actor_id: &DbUrl,
|
||||
) -> Result<Vec<Value>, Error> {
|
||||
use lemmy_db_schema::schema::activity::dsl::*;
|
||||
let res: Vec<Value> = activity
|
||||
.select(data)
|
||||
.filter(
|
||||
sql("activity.data ->> 'type' = 'Announce'")
|
||||
.sql(" AND activity.data -> 'object' ->> 'type' = 'Create'")
|
||||
.sql(" AND activity.data -> 'object' -> 'object' ->> 'type' = 'Page'")
|
||||
.sql(" AND activity.data ->> 'actor' = ")
|
||||
.bind::<Text, _>(community_actor_id)
|
||||
.sql(" ORDER BY activity.published DESC"),
|
||||
)
|
||||
.limit(20)
|
||||
.get_results(conn)?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
establish_unpooled_connection,
|
||||
source::activity::Activity_,
|
||||
Crud,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use super::*;
|
||||
use crate::{establish_unpooled_connection, source::activity::Activity_};
|
||||
use lemmy_db_schema::source::{
|
||||
activity::{Activity, ActivityForm},
|
||||
user::{UserForm, User_},
|
||||
person::{Person, PersonForm},
|
||||
};
|
||||
use serde_json::Value;
|
||||
use serial_test::serial;
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let creator_form = UserForm {
|
||||
let creator_form = PersonForm {
|
||||
name: "activity_creator_pm".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_creator = User_::create(&conn, &creator_form).unwrap();
|
||||
let inserted_creator = Person::create(&conn, &creator_form).unwrap();
|
||||
|
||||
let ap_id =
|
||||
"https://enterprise.lemmy.ml/activities/delete/f1b5d57c-80f8-4e03-a615-688d552e946c";
|
||||
let ap_id: DbUrl = Url::parse(
|
||||
"https://enterprise.lemmy.ml/activities/delete/f1b5d57c-80f8-4e03-a615-688d552e946c",
|
||||
)
|
||||
.unwrap()
|
||||
.into();
|
||||
let test_json: Value = serde_json::from_str(
|
||||
r#"{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
|
@ -150,7 +177,7 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
let activity_form = ActivityForm {
|
||||
ap_id: ap_id.to_string(),
|
||||
ap_id: ap_id.clone(),
|
||||
data: test_json.to_owned(),
|
||||
local: true,
|
||||
sensitive: false,
|
||||
|
@ -160,7 +187,7 @@ mod tests {
|
|||
let inserted_activity = Activity::create(&conn, &activity_form).unwrap();
|
||||
|
||||
let expected_activity = Activity {
|
||||
ap_id: Some(ap_id.to_string()),
|
||||
ap_id: Some(ap_id.clone()),
|
||||
id: inserted_activity.id,
|
||||
data: test_json,
|
||||
local: true,
|
||||
|
@ -170,8 +197,8 @@ mod tests {
|
|||
};
|
||||
|
||||
let read_activity = Activity::read(&conn, inserted_activity.id).unwrap();
|
||||
let read_activity_by_apub_id = Activity::read_from_apub_id(&conn, ap_id).unwrap();
|
||||
User_::delete(&conn, inserted_creator.id).unwrap();
|
||||
let read_activity_by_apub_id = Activity::read_from_apub_id(&conn, &ap_id).unwrap();
|
||||
Person::delete(&conn, inserted_creator.id).unwrap();
|
||||
Activity::delete(&conn, inserted_activity.id).unwrap();
|
||||
|
||||
assert_eq!(expected_activity, read_activity);
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
use crate::Crud;
|
||||
use diesel::{dsl::*, result::Error, *};
|
||||
use lemmy_db_schema::{schema::category::dsl::*, source::category::*};
|
||||
|
||||
impl Crud<CategoryForm> for Category {
|
||||
fn read(conn: &PgConnection, category_id: i32) -> Result<Self, Error> {
|
||||
category.find(category_id).first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn create(conn: &PgConnection, new_category: &CategoryForm) -> Result<Self, Error> {
|
||||
insert_into(category)
|
||||
.values(new_category)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn update(
|
||||
conn: &PgConnection,
|
||||
category_id: i32,
|
||||
new_category: &CategoryForm,
|
||||
) -> Result<Self, Error> {
|
||||
diesel::update(category.find(category_id))
|
||||
.set(new_category)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Category_ {
|
||||
fn list_all(conn: &PgConnection) -> Result<Vec<Category>, Error>;
|
||||
}
|
||||
|
||||
impl Category_ for Category {
|
||||
fn list_all(conn: &PgConnection) -> Result<Vec<Category>, Error> {
|
||||
category.load::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{establish_unpooled_connection, source::category::Category_};
|
||||
use lemmy_db_schema::source::category::Category;
|
||||
|
||||
#[test]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let categories = Category::list_all(&conn).unwrap();
|
||||
let expected_first_category = Category {
|
||||
id: 1,
|
||||
name: "Discussion".into(),
|
||||
};
|
||||
|
||||
assert_eq!(expected_first_category, categories[0]);
|
||||
}
|
||||
}
|
|
@ -10,39 +10,54 @@ use lemmy_db_schema::{
|
|||
CommentSaved,
|
||||
CommentSavedForm,
|
||||
},
|
||||
CommentId,
|
||||
DbUrl,
|
||||
PersonId,
|
||||
};
|
||||
|
||||
pub trait Comment_ {
|
||||
fn update_ap_id(conn: &PgConnection, comment_id: i32, apub_id: String) -> Result<Comment, Error>;
|
||||
fn update_ap_id(
|
||||
conn: &PgConnection,
|
||||
comment_id: CommentId,
|
||||
apub_id: DbUrl,
|
||||
) -> Result<Comment, Error>;
|
||||
fn permadelete_for_creator(
|
||||
conn: &PgConnection,
|
||||
for_creator_id: i32,
|
||||
for_creator_id: PersonId,
|
||||
) -> Result<Vec<Comment>, Error>;
|
||||
fn update_deleted(
|
||||
conn: &PgConnection,
|
||||
comment_id: i32,
|
||||
comment_id: CommentId,
|
||||
new_deleted: bool,
|
||||
) -> Result<Comment, Error>;
|
||||
fn update_removed(
|
||||
conn: &PgConnection,
|
||||
comment_id: i32,
|
||||
comment_id: CommentId,
|
||||
new_removed: bool,
|
||||
) -> Result<Comment, Error>;
|
||||
fn update_removed_for_creator(
|
||||
conn: &PgConnection,
|
||||
for_creator_id: i32,
|
||||
for_creator_id: PersonId,
|
||||
new_removed: bool,
|
||||
) -> Result<Vec<Comment>, Error>;
|
||||
fn update_read(conn: &PgConnection, comment_id: i32, new_read: bool) -> Result<Comment, Error>;
|
||||
fn update_read(
|
||||
conn: &PgConnection,
|
||||
comment_id: CommentId,
|
||||
new_read: bool,
|
||||
) -> Result<Comment, Error>;
|
||||
fn update_content(
|
||||
conn: &PgConnection,
|
||||
comment_id: i32,
|
||||
comment_id: CommentId,
|
||||
new_content: &str,
|
||||
) -> Result<Comment, Error>;
|
||||
}
|
||||
|
||||
impl Comment_ for Comment {
|
||||
fn update_ap_id(conn: &PgConnection, comment_id: i32, apub_id: String) -> Result<Self, Error> {
|
||||
fn update_ap_id(
|
||||
conn: &PgConnection,
|
||||
comment_id: CommentId,
|
||||
apub_id: DbUrl,
|
||||
) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::comment::dsl::*;
|
||||
|
||||
diesel::update(comment.find(comment_id))
|
||||
|
@ -50,7 +65,10 @@ impl Comment_ for Comment {
|
|||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn permadelete_for_creator(conn: &PgConnection, for_creator_id: i32) -> Result<Vec<Self>, Error> {
|
||||
fn permadelete_for_creator(
|
||||
conn: &PgConnection,
|
||||
for_creator_id: PersonId,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
use lemmy_db_schema::schema::comment::dsl::*;
|
||||
diesel::update(comment.filter(creator_id.eq(for_creator_id)))
|
||||
.set((
|
||||
|
@ -63,7 +81,7 @@ impl Comment_ for Comment {
|
|||
|
||||
fn update_deleted(
|
||||
conn: &PgConnection,
|
||||
comment_id: i32,
|
||||
comment_id: CommentId,
|
||||
new_deleted: bool,
|
||||
) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::comment::dsl::*;
|
||||
|
@ -74,7 +92,7 @@ impl Comment_ for Comment {
|
|||
|
||||
fn update_removed(
|
||||
conn: &PgConnection,
|
||||
comment_id: i32,
|
||||
comment_id: CommentId,
|
||||
new_removed: bool,
|
||||
) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::comment::dsl::*;
|
||||
|
@ -85,7 +103,7 @@ impl Comment_ for Comment {
|
|||
|
||||
fn update_removed_for_creator(
|
||||
conn: &PgConnection,
|
||||
for_creator_id: i32,
|
||||
for_creator_id: PersonId,
|
||||
new_removed: bool,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
use lemmy_db_schema::schema::comment::dsl::*;
|
||||
|
@ -94,7 +112,11 @@ impl Comment_ for Comment {
|
|||
.get_results::<Self>(conn)
|
||||
}
|
||||
|
||||
fn update_read(conn: &PgConnection, comment_id: i32, new_read: bool) -> Result<Self, Error> {
|
||||
fn update_read(
|
||||
conn: &PgConnection,
|
||||
comment_id: CommentId,
|
||||
new_read: bool,
|
||||
) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::comment::dsl::*;
|
||||
diesel::update(comment.find(comment_id))
|
||||
.set(read.eq(new_read))
|
||||
|
@ -103,7 +125,7 @@ impl Comment_ for Comment {
|
|||
|
||||
fn update_content(
|
||||
conn: &PgConnection,
|
||||
comment_id: i32,
|
||||
comment_id: CommentId,
|
||||
new_content: &str,
|
||||
) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::comment::dsl::*;
|
||||
|
@ -113,13 +135,13 @@ impl Comment_ for Comment {
|
|||
}
|
||||
}
|
||||
|
||||
impl Crud<CommentForm> for Comment {
|
||||
fn read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
|
||||
impl Crud<CommentForm, CommentId> for Comment {
|
||||
fn read(conn: &PgConnection, comment_id: CommentId) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::comment::dsl::*;
|
||||
comment.find(comment_id).first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn delete(conn: &PgConnection, comment_id: i32) -> Result<usize, Error> {
|
||||
fn delete(conn: &PgConnection, comment_id: CommentId) -> Result<usize, Error> {
|
||||
use lemmy_db_schema::schema::comment::dsl::*;
|
||||
diesel::delete(comment.find(comment_id)).execute(conn)
|
||||
}
|
||||
|
@ -133,7 +155,7 @@ impl Crud<CommentForm> for Comment {
|
|||
|
||||
fn update(
|
||||
conn: &PgConnection,
|
||||
comment_id: i32,
|
||||
comment_id: CommentId,
|
||||
comment_form: &CommentForm,
|
||||
) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::comment::dsl::*;
|
||||
|
@ -144,7 +166,7 @@ impl Crud<CommentForm> for Comment {
|
|||
}
|
||||
|
||||
impl ApubObject<CommentForm> for Comment {
|
||||
fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
|
||||
fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::comment::dsl::*;
|
||||
comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
|
||||
}
|
||||
|
@ -160,22 +182,26 @@ impl ApubObject<CommentForm> for Comment {
|
|||
}
|
||||
}
|
||||
|
||||
impl Likeable<CommentLikeForm> for CommentLike {
|
||||
impl Likeable<CommentLikeForm, CommentId> for CommentLike {
|
||||
fn like(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::comment_like::dsl::*;
|
||||
insert_into(comment_like)
|
||||
.values(comment_like_form)
|
||||
.on_conflict((comment_id, user_id))
|
||||
.on_conflict((comment_id, person_id))
|
||||
.do_update()
|
||||
.set(comment_like_form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
fn remove(conn: &PgConnection, user_id: i32, comment_id: i32) -> Result<usize, Error> {
|
||||
fn remove(
|
||||
conn: &PgConnection,
|
||||
person_id: PersonId,
|
||||
comment_id: CommentId,
|
||||
) -> Result<usize, Error> {
|
||||
use lemmy_db_schema::schema::comment_like::dsl;
|
||||
diesel::delete(
|
||||
dsl::comment_like
|
||||
.filter(dsl::comment_id.eq(comment_id))
|
||||
.filter(dsl::user_id.eq(user_id)),
|
||||
.filter(dsl::person_id.eq(person_id)),
|
||||
)
|
||||
.execute(conn)
|
||||
}
|
||||
|
@ -186,7 +212,7 @@ impl Saveable<CommentSavedForm> for CommentSaved {
|
|||
use lemmy_db_schema::schema::comment_saved::dsl::*;
|
||||
insert_into(comment_saved)
|
||||
.values(comment_saved_form)
|
||||
.on_conflict((comment_id, user_id))
|
||||
.on_conflict((comment_id, person_id))
|
||||
.do_update()
|
||||
.set(comment_saved_form)
|
||||
.get_result::<Self>(conn)
|
||||
|
@ -196,7 +222,7 @@ impl Saveable<CommentSavedForm> for CommentSaved {
|
|||
diesel::delete(
|
||||
comment_saved
|
||||
.filter(comment_id.eq(comment_saved_form.comment_id))
|
||||
.filter(user_id.eq(comment_saved_form.user_id)),
|
||||
.filter(person_id.eq(comment_saved_form.person_id)),
|
||||
)
|
||||
.execute(conn)
|
||||
}
|
||||
|
@ -204,53 +230,46 @@ impl Saveable<CommentSavedForm> for CommentSaved {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{establish_unpooled_connection, Crud, Likeable, ListingType, Saveable, SortType};
|
||||
use crate::{establish_unpooled_connection, Crud, Likeable, Saveable};
|
||||
use lemmy_db_schema::source::{
|
||||
comment::*,
|
||||
community::{Community, CommunityForm},
|
||||
person::{Person, PersonForm},
|
||||
post::*,
|
||||
user::{UserForm, User_},
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let new_user = UserForm {
|
||||
let new_person = PersonForm {
|
||||
name: "terry".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
||||
|
||||
let new_community = CommunityForm {
|
||||
name: "test community".to_string(),
|
||||
title: "nada".to_owned(),
|
||||
description: None,
|
||||
category_id: 1,
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
updated: None,
|
||||
|
@ -263,13 +282,16 @@ mod tests {
|
|||
published: None,
|
||||
banner: None,
|
||||
icon: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
followers_url: None,
|
||||
};
|
||||
|
||||
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||
|
||||
let new_post = PostForm {
|
||||
name: "A test post".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
url: None,
|
||||
body: None,
|
||||
community_id: inserted_community.id,
|
||||
|
@ -292,7 +314,7 @@ mod tests {
|
|||
|
||||
let comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -309,7 +331,7 @@ mod tests {
|
|||
let expected_comment = Comment {
|
||||
id: inserted_comment.id,
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: false,
|
||||
deleted: false,
|
||||
|
@ -323,7 +345,7 @@ mod tests {
|
|||
|
||||
let child_comment_form = CommentForm {
|
||||
content: "A child comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
parent_id: Some(inserted_comment.id),
|
||||
removed: None,
|
||||
|
@ -341,7 +363,7 @@ mod tests {
|
|||
let comment_like_form = CommentLikeForm {
|
||||
comment_id: inserted_comment.id,
|
||||
post_id: inserted_post.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
score: 1,
|
||||
};
|
||||
|
||||
|
@ -351,7 +373,7 @@ mod tests {
|
|||
id: inserted_comment_like.id,
|
||||
comment_id: inserted_comment.id,
|
||||
post_id: inserted_post.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
published: inserted_comment_like.published,
|
||||
score: 1,
|
||||
};
|
||||
|
@ -359,7 +381,7 @@ mod tests {
|
|||
// Comment Saved
|
||||
let comment_saved_form = CommentSavedForm {
|
||||
comment_id: inserted_comment.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
};
|
||||
|
||||
let inserted_comment_saved = CommentSaved::save(&conn, &comment_saved_form).unwrap();
|
||||
|
@ -367,19 +389,19 @@ mod tests {
|
|||
let expected_comment_saved = CommentSaved {
|
||||
id: inserted_comment_saved.id,
|
||||
comment_id: inserted_comment.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
published: inserted_comment_saved.published,
|
||||
};
|
||||
|
||||
let read_comment = Comment::read(&conn, inserted_comment.id).unwrap();
|
||||
let updated_comment = Comment::update(&conn, inserted_comment.id, &comment_form).unwrap();
|
||||
let like_removed = CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
|
||||
let like_removed = CommentLike::remove(&conn, inserted_person.id, inserted_comment.id).unwrap();
|
||||
let saved_removed = CommentSaved::unsave(&conn, &comment_saved_form).unwrap();
|
||||
let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
|
||||
Comment::delete(&conn, inserted_child_comment.id).unwrap();
|
||||
Post::delete(&conn, inserted_post.id).unwrap();
|
||||
Community::delete(&conn, inserted_community.id).unwrap();
|
||||
User_::delete(&conn, inserted_user.id).unwrap();
|
||||
Person::delete(&conn, inserted_person.id).unwrap();
|
||||
|
||||
assert_eq!(expected_comment, read_comment);
|
||||
assert_eq!(expected_comment, inserted_comment);
|
||||
|
|
|
@ -3,6 +3,7 @@ use diesel::{dsl::*, result::Error, *};
|
|||
use lemmy_db_schema::{
|
||||
naive_now,
|
||||
source::comment_report::{CommentReport, CommentReportForm},
|
||||
PersonId,
|
||||
};
|
||||
|
||||
impl Reportable<CommentReportForm> for CommentReport {
|
||||
|
@ -22,7 +23,11 @@ impl Reportable<CommentReportForm> for CommentReport {
|
|||
/// * `conn` - the postgres connection
|
||||
/// * `report_id` - the id of the report to resolve
|
||||
/// * `by_resolver_id` - the id of the user resolving the report
|
||||
fn resolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
|
||||
fn resolve(
|
||||
conn: &PgConnection,
|
||||
report_id: i32,
|
||||
by_resolver_id: PersonId,
|
||||
) -> Result<usize, Error> {
|
||||
use lemmy_db_schema::schema::comment_report::dsl::*;
|
||||
update(comment_report.find(report_id))
|
||||
.set((
|
||||
|
@ -38,7 +43,11 @@ impl Reportable<CommentReportForm> for CommentReport {
|
|||
/// * `conn` - the postgres connection
|
||||
/// * `report_id` - the id of the report to unresolve
|
||||
/// * `by_resolver_id` - the id of the user unresolving the report
|
||||
fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
|
||||
fn unresolve(
|
||||
conn: &PgConnection,
|
||||
report_id: i32,
|
||||
by_resolver_id: PersonId,
|
||||
) -> Result<usize, Error> {
|
||||
use lemmy_db_schema::schema::comment_report::dsl::*;
|
||||
update(comment_report.find(report_id))
|
||||
.set((
|
||||
|
|
|
@ -9,9 +9,12 @@ use lemmy_db_schema::{
|
|||
CommunityForm,
|
||||
CommunityModerator,
|
||||
CommunityModeratorForm,
|
||||
CommunityUserBan,
|
||||
CommunityUserBanForm,
|
||||
CommunityPersonBan,
|
||||
CommunityPersonBanForm,
|
||||
},
|
||||
CommunityId,
|
||||
DbUrl,
|
||||
PersonId,
|
||||
};
|
||||
|
||||
mod safe_type {
|
||||
|
@ -23,7 +26,6 @@ mod safe_type {
|
|||
name,
|
||||
title,
|
||||
description,
|
||||
category_id,
|
||||
creator_id,
|
||||
removed,
|
||||
published,
|
||||
|
@ -44,7 +46,6 @@ mod safe_type {
|
|||
name,
|
||||
title,
|
||||
description,
|
||||
category_id,
|
||||
creator_id,
|
||||
removed,
|
||||
published,
|
||||
|
@ -60,13 +61,13 @@ mod safe_type {
|
|||
}
|
||||
}
|
||||
|
||||
impl Crud<CommunityForm> for Community {
|
||||
fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
|
||||
impl Crud<CommunityForm, CommunityId> for Community {
|
||||
fn read(conn: &PgConnection, community_id: CommunityId) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::community::dsl::*;
|
||||
community.find(community_id).first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn delete(conn: &PgConnection, community_id: i32) -> Result<usize, Error> {
|
||||
fn delete(conn: &PgConnection, community_id: CommunityId) -> Result<usize, Error> {
|
||||
use lemmy_db_schema::schema::community::dsl::*;
|
||||
diesel::delete(community.find(community_id)).execute(conn)
|
||||
}
|
||||
|
@ -80,7 +81,7 @@ impl Crud<CommunityForm> for Community {
|
|||
|
||||
fn update(
|
||||
conn: &PgConnection,
|
||||
community_id: i32,
|
||||
community_id: CommunityId,
|
||||
new_community: &CommunityForm,
|
||||
) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::community::dsl::*;
|
||||
|
@ -91,7 +92,7 @@ impl Crud<CommunityForm> for Community {
|
|||
}
|
||||
|
||||
impl ApubObject<CommunityForm> for Community {
|
||||
fn read_from_apub_id(conn: &PgConnection, for_actor_id: &str) -> Result<Self, Error> {
|
||||
fn read_from_apub_id(conn: &PgConnection, for_actor_id: &DbUrl) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::community::dsl::*;
|
||||
community
|
||||
.filter(actor_id.eq(for_actor_id))
|
||||
|
@ -113,25 +114,29 @@ pub trait Community_ {
|
|||
fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Community, Error>;
|
||||
fn update_deleted(
|
||||
conn: &PgConnection,
|
||||
community_id: i32,
|
||||
community_id: CommunityId,
|
||||
new_deleted: bool,
|
||||
) -> Result<Community, Error>;
|
||||
fn update_removed(
|
||||
conn: &PgConnection,
|
||||
community_id: i32,
|
||||
community_id: CommunityId,
|
||||
new_removed: bool,
|
||||
) -> Result<Community, Error>;
|
||||
fn update_removed_for_creator(
|
||||
conn: &PgConnection,
|
||||
for_creator_id: i32,
|
||||
for_creator_id: PersonId,
|
||||
new_removed: bool,
|
||||
) -> Result<Vec<Community>, Error>;
|
||||
fn update_creator(
|
||||
conn: &PgConnection,
|
||||
community_id: i32,
|
||||
new_creator_id: i32,
|
||||
community_id: CommunityId,
|
||||
new_creator_id: PersonId,
|
||||
) -> Result<Community, Error>;
|
||||
fn distinct_federated_communities(conn: &PgConnection) -> Result<Vec<String>, Error>;
|
||||
fn read_from_followers_url(
|
||||
conn: &PgConnection,
|
||||
followers_url: &DbUrl,
|
||||
) -> Result<Community, Error>;
|
||||
}
|
||||
|
||||
impl Community_ for Community {
|
||||
|
@ -145,7 +150,7 @@ impl Community_ for Community {
|
|||
|
||||
fn update_deleted(
|
||||
conn: &PgConnection,
|
||||
community_id: i32,
|
||||
community_id: CommunityId,
|
||||
new_deleted: bool,
|
||||
) -> Result<Community, Error> {
|
||||
use lemmy_db_schema::schema::community::dsl::*;
|
||||
|
@ -156,7 +161,7 @@ impl Community_ for Community {
|
|||
|
||||
fn update_removed(
|
||||
conn: &PgConnection,
|
||||
community_id: i32,
|
||||
community_id: CommunityId,
|
||||
new_removed: bool,
|
||||
) -> Result<Community, Error> {
|
||||
use lemmy_db_schema::schema::community::dsl::*;
|
||||
|
@ -167,7 +172,7 @@ impl Community_ for Community {
|
|||
|
||||
fn update_removed_for_creator(
|
||||
conn: &PgConnection,
|
||||
for_creator_id: i32,
|
||||
for_creator_id: PersonId,
|
||||
new_removed: bool,
|
||||
) -> Result<Vec<Community>, Error> {
|
||||
use lemmy_db_schema::schema::community::dsl::*;
|
||||
|
@ -178,8 +183,8 @@ impl Community_ for Community {
|
|||
|
||||
fn update_creator(
|
||||
conn: &PgConnection,
|
||||
community_id: i32,
|
||||
new_creator_id: i32,
|
||||
community_id: CommunityId,
|
||||
new_creator_id: PersonId,
|
||||
) -> Result<Community, Error> {
|
||||
use lemmy_db_schema::schema::community::dsl::*;
|
||||
diesel::update(community.find(community_id))
|
||||
|
@ -191,79 +196,95 @@ impl Community_ for Community {
|
|||
use lemmy_db_schema::schema::community::dsl::*;
|
||||
community.select(actor_id).distinct().load::<String>(conn)
|
||||
}
|
||||
|
||||
fn read_from_followers_url(
|
||||
conn: &PgConnection,
|
||||
followers_url_: &DbUrl,
|
||||
) -> Result<Community, Error> {
|
||||
use lemmy_db_schema::schema::community::dsl::*;
|
||||
community
|
||||
.filter(followers_url.eq(followers_url_))
|
||||
.first::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Joinable<CommunityModeratorForm> for CommunityModerator {
|
||||
fn join(
|
||||
conn: &PgConnection,
|
||||
community_user_form: &CommunityModeratorForm,
|
||||
community_moderator_form: &CommunityModeratorForm,
|
||||
) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::community_moderator::dsl::*;
|
||||
insert_into(community_moderator)
|
||||
.values(community_user_form)
|
||||
.values(community_moderator_form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn leave(
|
||||
conn: &PgConnection,
|
||||
community_user_form: &CommunityModeratorForm,
|
||||
community_moderator_form: &CommunityModeratorForm,
|
||||
) -> Result<usize, Error> {
|
||||
use lemmy_db_schema::schema::community_moderator::dsl::*;
|
||||
diesel::delete(
|
||||
community_moderator
|
||||
.filter(community_id.eq(community_user_form.community_id))
|
||||
.filter(user_id.eq(community_user_form.user_id)),
|
||||
.filter(community_id.eq(community_moderator_form.community_id))
|
||||
.filter(person_id.eq(community_moderator_form.person_id)),
|
||||
)
|
||||
.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CommunityModerator_ {
|
||||
fn delete_for_community(conn: &PgConnection, for_community_id: i32) -> Result<usize, Error>;
|
||||
fn get_user_moderated_communities(
|
||||
fn delete_for_community(
|
||||
conn: &PgConnection,
|
||||
for_user_id: i32,
|
||||
) -> Result<Vec<i32>, Error>;
|
||||
for_community_id: CommunityId,
|
||||
) -> Result<usize, Error>;
|
||||
fn get_person_moderated_communities(
|
||||
conn: &PgConnection,
|
||||
for_person_id: PersonId,
|
||||
) -> Result<Vec<CommunityId>, Error>;
|
||||
}
|
||||
|
||||
impl CommunityModerator_ for CommunityModerator {
|
||||
fn delete_for_community(conn: &PgConnection, for_community_id: i32) -> Result<usize, Error> {
|
||||
fn delete_for_community(
|
||||
conn: &PgConnection,
|
||||
for_community_id: CommunityId,
|
||||
) -> Result<usize, Error> {
|
||||
use lemmy_db_schema::schema::community_moderator::dsl::*;
|
||||
diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn)
|
||||
}
|
||||
|
||||
fn get_user_moderated_communities(
|
||||
fn get_person_moderated_communities(
|
||||
conn: &PgConnection,
|
||||
for_user_id: i32,
|
||||
) -> Result<Vec<i32>, Error> {
|
||||
for_person_id: PersonId,
|
||||
) -> Result<Vec<CommunityId>, Error> {
|
||||
use lemmy_db_schema::schema::community_moderator::dsl::*;
|
||||
community_moderator
|
||||
.filter(user_id.eq(for_user_id))
|
||||
.filter(person_id.eq(for_person_id))
|
||||
.select(community_id)
|
||||
.load::<i32>(conn)
|
||||
.load::<CommunityId>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bannable<CommunityUserBanForm> for CommunityUserBan {
|
||||
impl Bannable<CommunityPersonBanForm> for CommunityPersonBan {
|
||||
fn ban(
|
||||
conn: &PgConnection,
|
||||
community_user_ban_form: &CommunityUserBanForm,
|
||||
community_person_ban_form: &CommunityPersonBanForm,
|
||||
) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::community_user_ban::dsl::*;
|
||||
insert_into(community_user_ban)
|
||||
.values(community_user_ban_form)
|
||||
use lemmy_db_schema::schema::community_person_ban::dsl::*;
|
||||
insert_into(community_person_ban)
|
||||
.values(community_person_ban_form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn unban(
|
||||
conn: &PgConnection,
|
||||
community_user_ban_form: &CommunityUserBanForm,
|
||||
community_person_ban_form: &CommunityPersonBanForm,
|
||||
) -> Result<usize, Error> {
|
||||
use lemmy_db_schema::schema::community_user_ban::dsl::*;
|
||||
use lemmy_db_schema::schema::community_person_ban::dsl::*;
|
||||
diesel::delete(
|
||||
community_user_ban
|
||||
.filter(community_id.eq(community_user_ban_form.community_id))
|
||||
.filter(user_id.eq(community_user_ban_form.user_id)),
|
||||
community_person_ban
|
||||
.filter(community_id.eq(community_person_ban_form.community_id))
|
||||
.filter(person_id.eq(community_person_ban_form.person_id)),
|
||||
)
|
||||
.execute(conn)
|
||||
}
|
||||
|
@ -277,12 +298,16 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
|
|||
use lemmy_db_schema::schema::community_follower::dsl::*;
|
||||
insert_into(community_follower)
|
||||
.values(community_follower_form)
|
||||
.on_conflict((community_id, user_id))
|
||||
.on_conflict((community_id, person_id))
|
||||
.do_update()
|
||||
.set(community_follower_form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
fn follow_accepted(conn: &PgConnection, community_id_: i32, user_id_: i32) -> Result<Self, Error>
|
||||
fn follow_accepted(
|
||||
conn: &PgConnection,
|
||||
community_id_: CommunityId,
|
||||
person_id_: PersonId,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -290,7 +315,7 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
|
|||
diesel::update(
|
||||
community_follower
|
||||
.filter(community_id.eq(community_id_))
|
||||
.filter(user_id.eq(user_id_)),
|
||||
.filter(person_id.eq(person_id_)),
|
||||
)
|
||||
.set(pending.eq(true))
|
||||
.get_result::<Self>(conn)
|
||||
|
@ -303,13 +328,13 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
|
|||
diesel::delete(
|
||||
community_follower
|
||||
.filter(community_id.eq(&community_follower_form.community_id))
|
||||
.filter(user_id.eq(&community_follower_form.user_id)),
|
||||
.filter(person_id.eq(&community_follower_form.person_id)),
|
||||
)
|
||||
.execute(conn)
|
||||
}
|
||||
// TODO: this function name only makes sense if you call it with a remote community. for a local
|
||||
// community, it will also return true if only remote followers exist
|
||||
fn has_local_followers(conn: &PgConnection, community_id_: i32) -> Result<bool, Error> {
|
||||
fn has_local_followers(conn: &PgConnection, community_id_: CommunityId) -> Result<bool, Error> {
|
||||
use lemmy_db_schema::schema::community_follower::dsl::*;
|
||||
diesel::select(exists(
|
||||
community_follower.filter(community_id.eq(community_id_)),
|
||||
|
@ -320,56 +345,41 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
establish_unpooled_connection,
|
||||
Bannable,
|
||||
Crud,
|
||||
Followable,
|
||||
Joinable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_schema::source::{community::*, user::*};
|
||||
use crate::{establish_unpooled_connection, Bannable, Crud, Followable, Joinable};
|
||||
use lemmy_db_schema::source::{community::*, person::*};
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let new_user = UserForm {
|
||||
let new_person = PersonForm {
|
||||
name: "bobbee".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
||||
|
||||
let new_community = CommunityForm {
|
||||
name: "TIL".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
title: "nada".to_owned(),
|
||||
description: None,
|
||||
category_id: 1,
|
||||
nsfw: false,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -382,17 +392,19 @@ mod tests {
|
|||
published: None,
|
||||
icon: None,
|
||||
banner: None,
|
||||
followers_url: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||
|
||||
let expected_community = Community {
|
||||
id: inserted_community.id,
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
name: "TIL".into(),
|
||||
title: "nada".to_owned(),
|
||||
description: None,
|
||||
category_id: 1,
|
||||
nsfw: false,
|
||||
removed: false,
|
||||
deleted: false,
|
||||
|
@ -405,11 +417,14 @@ mod tests {
|
|||
last_refreshed_at: inserted_community.published,
|
||||
icon: None,
|
||||
banner: None,
|
||||
followers_url: inserted_community.followers_url.to_owned(),
|
||||
inbox_url: inserted_community.inbox_url.to_owned(),
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let community_follower_form = CommunityFollowerForm {
|
||||
community_id: inserted_community.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
|
@ -419,55 +434,56 @@ mod tests {
|
|||
let expected_community_follower = CommunityFollower {
|
||||
id: inserted_community_follower.id,
|
||||
community_id: inserted_community.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
pending: Some(false),
|
||||
published: inserted_community_follower.published,
|
||||
};
|
||||
|
||||
let community_user_form = CommunityModeratorForm {
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
community_id: inserted_community.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
};
|
||||
|
||||
let inserted_community_user = CommunityModerator::join(&conn, &community_user_form).unwrap();
|
||||
let inserted_community_moderator =
|
||||
CommunityModerator::join(&conn, &community_moderator_form).unwrap();
|
||||
|
||||
let expected_community_user = CommunityModerator {
|
||||
id: inserted_community_user.id,
|
||||
let expected_community_moderator = CommunityModerator {
|
||||
id: inserted_community_moderator.id,
|
||||
community_id: inserted_community.id,
|
||||
user_id: inserted_user.id,
|
||||
published: inserted_community_user.published,
|
||||
person_id: inserted_person.id,
|
||||
published: inserted_community_moderator.published,
|
||||
};
|
||||
|
||||
let community_user_ban_form = CommunityUserBanForm {
|
||||
let community_person_ban_form = CommunityPersonBanForm {
|
||||
community_id: inserted_community.id,
|
||||
user_id: inserted_user.id,
|
||||
person_id: inserted_person.id,
|
||||
};
|
||||
|
||||
let inserted_community_user_ban =
|
||||
CommunityUserBan::ban(&conn, &community_user_ban_form).unwrap();
|
||||
let inserted_community_person_ban =
|
||||
CommunityPersonBan::ban(&conn, &community_person_ban_form).unwrap();
|
||||
|
||||
let expected_community_user_ban = CommunityUserBan {
|
||||
id: inserted_community_user_ban.id,
|
||||
let expected_community_person_ban = CommunityPersonBan {
|
||||
id: inserted_community_person_ban.id,
|
||||
community_id: inserted_community.id,
|
||||
user_id: inserted_user.id,
|
||||
published: inserted_community_user_ban.published,
|
||||
person_id: inserted_person.id,
|
||||
published: inserted_community_person_ban.published,
|
||||
};
|
||||
|
||||
let read_community = Community::read(&conn, inserted_community.id).unwrap();
|
||||
let updated_community =
|
||||
Community::update(&conn, inserted_community.id, &new_community).unwrap();
|
||||
let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap();
|
||||
let left_community = CommunityModerator::leave(&conn, &community_user_form).unwrap();
|
||||
let unban = CommunityUserBan::unban(&conn, &community_user_ban_form).unwrap();
|
||||
let left_community = CommunityModerator::leave(&conn, &community_moderator_form).unwrap();
|
||||
let unban = CommunityPersonBan::unban(&conn, &community_person_ban_form).unwrap();
|
||||
let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
|
||||
User_::delete(&conn, inserted_user.id).unwrap();
|
||||
Person::delete(&conn, inserted_person.id).unwrap();
|
||||
|
||||
assert_eq!(expected_community, read_community);
|
||||
assert_eq!(expected_community, inserted_community);
|
||||
assert_eq!(expected_community, updated_community);
|
||||
assert_eq!(expected_community_follower, inserted_community_follower);
|
||||
assert_eq!(expected_community_user, inserted_community_user);
|
||||
assert_eq!(expected_community_user_ban, inserted_community_user_ban);
|
||||
assert_eq!(expected_community_moderator, inserted_community_moderator);
|
||||
assert_eq!(expected_community_person_ban, inserted_community_person_ban);
|
||||
assert_eq!(1, ignored_community);
|
||||
assert_eq!(1, left_community);
|
||||
assert_eq!(1, unban);
|
||||
|
|
119
crates/db_queries/src/source/local_user.rs
Normal file
119
crates/db_queries/src/source/local_user.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
pub mod activity;
|
||||
pub mod category;
|
||||
pub mod comment;
|
||||
pub mod comment_report;
|
||||
pub mod community;
|
||||
pub mod local_user;
|
||||
pub mod moderator;
|
||||
pub mod password_reset_request;
|
||||
pub mod person;
|
||||
pub mod person_mention;
|
||||
pub mod post;
|
||||
pub mod post_report;
|
||||
pub mod private_message;
|
||||
pub mod site;
|
||||
pub mod user;
|
||||
pub mod user_mention;
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::Crud;
|
|||
use diesel::{dsl::*, result::Error, *};
|
||||
use lemmy_db_schema::source::moderator::*;
|
||||
|
||||
impl Crud<ModRemovePostForm> for ModRemovePost {
|
||||
impl Crud<ModRemovePostForm, i32> for ModRemovePost {
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::mod_remove_post::dsl::*;
|
||||
mod_remove_post.find(from_id).first::<Self>(conn)
|
||||
|
@ -23,7 +23,7 @@ impl Crud<ModRemovePostForm> for ModRemovePost {
|
|||
}
|
||||
}
|
||||
|
||||
impl Crud<ModLockPostForm> for ModLockPost {
|
||||
impl Crud<ModLockPostForm, i32> for ModLockPost {
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::mod_lock_post::dsl::*;
|
||||
mod_lock_post.find(from_id).first::<Self>(conn)
|
||||
|
@ -44,7 +44,7 @@ impl Crud<ModLockPostForm> for ModLockPost {
|
|||
}
|
||||
}
|
||||
|
||||
impl Crud<ModStickyPostForm> for ModStickyPost {
|
||||
impl Crud<ModStickyPostForm, i32> for ModStickyPost {
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::mod_sticky_post::dsl::*;
|
||||
mod_sticky_post.find(from_id).first::<Self>(conn)
|
||||
|
@ -65,7 +65,7 @@ impl Crud<ModStickyPostForm> for ModStickyPost {
|
|||
}
|
||||
}
|
||||
|
||||
impl Crud<ModRemoveCommentForm> for ModRemoveComment {
|
||||
impl Crud<ModRemoveCommentForm, i32> for ModRemoveComment {
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::mod_remove_comment::dsl::*;
|
||||
mod_remove_comment.find(from_id).first::<Self>(conn)
|
||||
|
@ -86,7 +86,7 @@ impl Crud<ModRemoveCommentForm> for ModRemoveComment {
|
|||
}
|
||||
}
|
||||
|
||||
impl Crud<ModRemoveCommunityForm> for ModRemoveCommunity {
|
||||
impl Crud<ModRemoveCommunityForm, i32> for ModRemoveCommunity {
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::mod_remove_community::dsl::*;
|
||||
mod_remove_community.find(from_id).first::<Self>(conn)
|
||||
|
@ -111,7 +111,7 @@ impl Crud<ModRemoveCommunityForm> for ModRemoveCommunity {
|
|||
}
|
||||
}
|
||||
|
||||
impl Crud<ModBanFromCommunityForm> for ModBanFromCommunity {
|
||||
impl Crud<ModBanFromCommunityForm, i32> for ModBanFromCommunity {
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::mod_ban_from_community::dsl::*;
|
||||
mod_ban_from_community.find(from_id).first::<Self>(conn)
|
||||
|
@ -136,7 +136,7 @@ impl Crud<ModBanFromCommunityForm> for ModBanFromCommunity {
|
|||
}
|
||||
}
|
||||
|
||||
impl Crud<ModBanForm> for ModBan {
|
||||
impl Crud<ModBanForm, i32> for ModBan {
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::mod_ban::dsl::*;
|
||||
mod_ban.find(from_id).first::<Self>(conn)
|
||||
|
@ -155,7 +155,7 @@ impl Crud<ModBanForm> for ModBan {
|
|||
}
|
||||
}
|
||||
|
||||
impl Crud<ModAddCommunityForm> for ModAddCommunity {
|
||||
impl Crud<ModAddCommunityForm, i32> for ModAddCommunity {
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::mod_add_community::dsl::*;
|
||||
mod_add_community.find(from_id).first::<Self>(conn)
|
||||
|
@ -176,7 +176,7 @@ impl Crud<ModAddCommunityForm> for ModAddCommunity {
|
|||
}
|
||||
}
|
||||
|
||||
impl Crud<ModAddForm> for ModAdd {
|
||||
impl Crud<ModAddForm, i32> for ModAdd {
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use lemmy_db_schema::schema::mod_add::dsl::*;
|
||||
mod_add.find(from_id).first::<Self>(conn)
|
||||
|
@ -197,78 +197,63 @@ impl Crud<ModAddForm> for ModAdd {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{establish_unpooled_connection, Crud, ListingType, SortType};
|
||||
use lemmy_db_schema::source::{comment::*, community::*, moderator::*, post::*, user::*};
|
||||
use crate::{establish_unpooled_connection, Crud};
|
||||
use lemmy_db_schema::source::{comment::*, community::*, moderator::*, person::*, post::*};
|
||||
use serial_test::serial;
|
||||
|
||||
// use Crud;
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let new_mod = UserForm {
|
||||
let new_mod = PersonForm {
|
||||
name: "the mod".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_mod = User_::create(&conn, &new_mod).unwrap();
|
||||
let inserted_mod = Person::create(&conn, &new_mod).unwrap();
|
||||
|
||||
let new_user = UserForm {
|
||||
let new_person = PersonForm {
|
||||
name: "jim2".into(),
|
||||
preferred_username: None,
|
||||
password_encrypted: "nope".into(),
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
avatar: None,
|
||||
banner: None,
|
||||
admin: false,
|
||||
banned: Some(false),
|
||||
banned: None,
|
||||
deleted: None,
|
||||
published: None,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "browser".into(),
|
||||
default_sort_type: SortType::Hot as i16,
|
||||
default_listing_type: ListingType::Subscribed as i16,
|
||||
lang: "browser".into(),
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
actor_id: None,
|
||||
bio: None,
|
||||
local: true,
|
||||
local: None,
|
||||
private_key: None,
|
||||
public_key: None,
|
||||
last_refreshed_at: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
||||
|
||||
let new_community = CommunityForm {
|
||||
name: "mod_community".to_string(),
|
||||
title: "nada".to_owned(),
|
||||
description: None,
|
||||
category_id: 1,
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
updated: None,
|
||||
|
@ -281,6 +266,9 @@ mod tests {
|
|||
published: None,
|
||||
icon: None,
|
||||
banner: None,
|
||||
followers_url: None,
|
||||
inbox_url: None,
|
||||
shared_inbox_url: None,
|
||||
};
|
||||
|
||||
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||
|
@ -289,7 +277,7 @@ mod tests {
|
|||
name: "A test post thweep".into(),
|
||||
url: None,
|
||||
body: None,
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
community_id: inserted_community.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -310,7 +298,7 @@ mod tests {
|
|||
|
||||
let comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_user.id,
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
removed: None,
|
||||
deleted: None,
|
||||
|
@ -328,7 +316,7 @@ mod tests {
|
|||
|
||||
// remove post
|
||||
let mod_remove_post_form = ModRemovePostForm {
|
||||
mod_user_id: inserted_mod.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
post_id: inserted_post.id,
|
||||
reason: None,
|
||||
removed: None,
|
||||
|
@ -338,7 +326,7 @@ mod tests {
|
|||
let expected_mod_remove_post = ModRemovePost {
|
||||
id: inserted_mod_remove_post.id,
|
||||
post_id: inserted_post.id,
|
||||
mod_user_id: inserted_mod.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
reason: None,
|
||||
removed: Some(true),
|
||||
when_: inserted_mod_remove_post.when_,
|
||||
|
@ -347,7 +335,7 @@ mod tests {
|
|||
// lock post
|
||||
|
||||
let mod_lock_post_form = ModLockPostForm {
|
||||
mod_user_id: inserted_mod.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
post_id: inserted_post.id,
|
||||
locked: None,
|
||||
};
|
||||
|
@ -356,7 +344,7 @@ mod tests {
|
|||
let expected_mod_lock_post = ModLockPost {
|
||||
id: inserted_mod_lock_post.id,
|
||||
post_id: inserted_post.id,
|
||||
mod_user_id: inserted_mod.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
locked: Some(true),
|
||||
when_: inserted_mod_lock_post.when_,
|
||||
};
|
||||
|
@ -364,7 +352,7 @@ mod tests {
|
|||
// sticky post
|
||||
|
||||
let mod_sticky_post_form = ModStickyPostForm {
|
||||
mod_user_id: inserted_mod.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
post_id: inserted_post.id,
|
||||
stickied: None,
|
||||
};
|
||||
|
@ -373,7 +361,7 @@ mod tests {
|
|||
let expected_mod_sticky_post = ModStickyPost {
|
||||
id: inserted_mod_sticky_post.id,
|
||||
post_id: inserted_post.id,
|
||||
mod_user_id: inserted_mod.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
stickied: Some(true),
|
||||
when_: inserted_mod_sticky_post.when_,
|
||||
};
|
||||
|
@ -381,7 +369,7 @@ mod tests {
|
|||
// comment
|
||||
|
||||
let mod_remove_comment_form = ModRemoveCommentForm {
|
||||
mod_user_id: inserted_mod.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
comment_id: inserted_comment.id,
|
||||
reason: None,
|
||||
removed: None,
|
||||
|
@ -393,7 +381,7 @@ mod tests {
|
|||
let expected_mod_remove_comment = ModRemoveComment {
|
||||
id: inserted_mod_remove_comment.id,
|
||||
comment_id: inserted_comment.id,
|
||||
mod_user_id: inserted_mod.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
reason: None,
|
||||
removed: Some(true),
|
||||
when_: inserted_mod_remove_comment.when_,
|
||||
|
@ -402,7 +390,7 @@ mod tests {
|
|||
// community
|
||||
|
||||
let mod_remove_community_form = ModRemoveCommunityForm {
|
||||
mod_user_id: inserted_mod.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
community_id: inserted_community.id,
|
||||
reason: None,
|
||||
removed: None,
|
||||
|
@ -415,7 +403,7 @@ mod tests {
|
|||
let expected_mod_remove_community = ModRemoveCommunity {
|
||||
id: inserted_mod_remove_community.id,
|
||||
community_id: inserted_community.id,
|
||||
mod_user_id: inserted_mod.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
reason: None,
|
||||
removed: Some(true),
|
||||
expires: None,
|
||||
|
@ -425,8 +413,8 @@ mod tests {
|
|||
// ban from community
|
||||
|
||||
let mod_ban_from_community_form = ModBanFromCommunityForm {
|
||||
mod_user_id: inserted_mod.id,
|
||||
other_user_id: inserted_user.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
other_person_id: inserted_person.id,
|
||||
community_id: inserted_community.id,
|
||||
reason: None,
|
||||
banned: None,
|
||||
|
@ -439,8 +427,8 @@ mod tests {
|
|||
let expected_mod_ban_from_community = ModBanFromCommunity {
|
||||
id: inserted_mod_ban_from_community.id,
|
||||
community_id: inserted_community.id,
|
||||
mod_user_id: inserted_mod.id,
|
||||
other_user_id: inserted_user.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
other_person_id: inserted_person.id,
|
||||
reason: None,
|
||||
banned: Some(true),
|
||||
expires: None,
|
||||
|
@ -450,8 +438,8 @@ mod tests {
|
|||
// ban
|
||||
|
||||
let mod_ban_form = ModBanForm {
|
||||
mod_user_id: inserted_mod.id,
|
||||
other_user_id: inserted_user.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
other_person_id: inserted_person.id,
|
||||
reason: None,
|
||||
banned: None,
|
||||
expires: None,
|
||||
|
@ -460,8 +448,8 @@ mod tests {
|
|||
let read_mod_ban = ModBan::read(&conn, inserted_mod_ban.id).unwrap();
|
||||
let expected_mod_ban = ModBan {
|
||||
id: inserted_mod_ban.id,
|
||||
mod_user_id: inserted_mod.id,
|
||||
other_user_id: inserted_user.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
other_person_id: inserted_person.id,
|
||||
reason: None,
|
||||
banned: Some(true),
|
||||
expires: None,
|
||||
|
@ -471,8 +459,8 @@ mod tests {
|
|||
// mod add community
|
||||
|
||||
let mod_add_community_form = ModAddCommunityForm {
|
||||
mod_user_id: inserted_mod.id,
|
||||
other_user_id: inserted_user.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
other_person_id: inserted_person.id,
|
||||
community_id: inserted_community.id,
|
||||
removed: None,
|
||||
};
|
||||
|
@ -483,8 +471,8 @@ mod tests {
|
|||
let expected_mod_add_community = ModAddCommunity {
|
||||
id: inserted_mod_add_community.id,
|
||||
community_id: inserted_community.id,
|
||||
mod_user_id: inserted_mod.id,
|
||||
other_user_id: inserted_user.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
other_person_id: inserted_person.id,
|
||||
removed: Some(false),
|
||||
when_: inserted_mod_add_community.when_,
|
||||
};
|
||||
|
@ -492,16 +480,16 @@ mod tests {
|
|||
// mod add
|
||||
|
||||
let mod_add_form = ModAddForm {
|
||||
mod_user_id: inserted_mod.id,
|
||||
other_user_id: inserted_user.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
other_person_id: inserted_person.id,
|
||||
removed: None,
|
||||
};
|
||||
let inserted_mod_add = ModAdd::create(&conn, &mod_add_form).unwrap();
|
||||
let read_mod_add = ModAdd::read(&conn, inserted_mod_add.id).unwrap();
|
||||
let expected_mod_add = ModAdd {
|
||||
id: inserted_mod_add.id,
|
||||
mod_user_id: inserted_mod.id,
|
||||
other_user_id: inserted_user.id,
|
||||
mod_person_id: inserted_mod.id,
|
||||
other_person_id: inserted_person.id,
|
||||
removed: Some(false),
|
||||
when_: inserted_mod_add.when_,
|
||||
};
|
||||
|
@ -509,8 +497,8 @@ mod tests {
|
|||
Comment::delete(&conn, inserted_comment.id).unwrap();
|
||||
Post::delete(&conn, inserted_post.id).unwrap();
|
||||
Community::delete(&conn, inserted_community.id).unwrap();
|
||||
User_::delete(&conn, inserted_user.id).unwrap();
|
||||
User_::delete(&conn, inserted_mod.id).unwrap();
|
||||
Person::delete(&conn, inserted_person.id).unwrap();
|
||||
Person::delete(&conn, inserted_mod.id).unwrap();
|
||||
|
||||
assert_eq!(expected_mod_remove_post, read_mod_remove_post);
|
||||
assert_eq!(expected_mod_lock_post, read_mod_lock_post);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue