Compare commits

..

No commits in common. "main" and "fetch-limit" have entirely different histories.

227 changed files with 5989 additions and 8211 deletions

View file

@ -8,8 +8,14 @@ platform:
steps: steps:
- name: fetch git submodules
image: node:15-alpine3.12
commands:
- apk add git
- git submodule update --init --recursive --remote
- name: chown repo - name: chown repo
image: ekidd/rust-musl-builder:1.50.0 image: ekidd/rust-musl-builder:1.47.0
user: root user: root
commands: commands:
- chown 1000:1000 . -R - chown 1000:1000 . -R
@ -20,33 +26,23 @@ steps:
- /root/.cargo/bin/cargo fmt -- --check - /root/.cargo/bin/cargo fmt -- --check
- name: cargo clippy - name: cargo clippy
image: ekidd/rust-musl-builder:1.50.0 image: ekidd/rust-musl-builder:1.47.0
environment:
CARGO_HOME: /drone/src/.cargo
commands: commands:
- whoami
- ls -la ~/.cargo
- mv ~/.cargo .
- ls -la .cargo
- cargo clippy --workspace --tests --all-targets --all-features -- -D warnings -D deprecated -D clippy::perf -D clippy::complexity -D clippy::dbg_macro - cargo clippy --workspace --tests --all-targets --all-features -- -D warnings -D deprecated -D clippy::perf -D clippy::complexity -D clippy::dbg_macro
- cargo clippy --workspace -- -D clippy::unwrap_used
- name: cargo test - name: cargo test
image: ekidd/rust-musl-builder:1.50.0 image: ekidd/rust-musl-builder:1.47.0
environment: environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
RUST_TEST_THREADS: 1 RUST_TEST_THREADS: 1
CARGO_HOME: /drone/src/.cargo
commands: commands:
- sudo apt-get update - sudo apt-get update
- sudo apt-get -y install --no-install-recommends espeak postgresql-client - sudo apt-get -y install --no-install-recommends espeak postgresql-client
- cargo test --workspace --no-fail-fast - cargo test --workspace --no-fail-fast
- name: cargo build - name: cargo build
image: ekidd/rust-musl-builder:1.50.0 image: ekidd/rust-musl-builder:1.47.0
environment:
CARGO_HOME: /drone/src/.cargo
commands: commands:
- cargo build - cargo build
- mv target/x86_64-unknown-linux-musl/debug/lemmy_server target/lemmy_server - mv target/x86_64-unknown-linux-musl/debug/lemmy_server target/lemmy_server
@ -112,13 +108,18 @@ platform:
steps: steps:
- name: fetch git submodules
image: node:15-alpine3.12
commands:
- apk add git
- git submodule update --init --recursive --remote
- name: cargo test - name: cargo test
image: rust:1.50-slim-buster image: rust:1.47-slim-buster
environment: environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
RUST_TEST_THREADS: 1 RUST_TEST_THREADS: 1
CARGO_HOME: /drone/src/.cargo
commands: commands:
- apt-get update - apt-get update
- apt-get -y install --no-install-recommends espeak postgresql-client libssl-dev pkg-config libpq-dev - apt-get -y install --no-install-recommends espeak postgresql-client libssl-dev pkg-config libpq-dev
@ -127,9 +128,7 @@ steps:
# Using Debian here because there seems to be no official Alpine-based Rust docker image for ARM. # Using Debian here because there seems to be no official Alpine-based Rust docker image for ARM.
- name: cargo build - name: cargo build
image: rust:1.50-slim-buster image: rust:1.47-slim-buster
environment:
CARGO_HOME: /drone/src/.cargo
commands: commands:
- apt-get update - apt-get update
- apt-get -y install --no-install-recommends libssl-dev pkg-config libpq-dev - apt-get -y install --no-install-recommends libssl-dev pkg-config libpq-dev

4
.gitmodules vendored Normal file
View file

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

View file

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

View file

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

View file

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

1081
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -1,66 +1,3 @@
# Lemmy v0.9.9 Release (2021-02-19)
## Changes
### Lemmy backend
- Added an federated activity query sorting order.
- Explicitly marking posts and comments as public.
- Added a `NewComment` / forum sort for posts.
- Fixed an issue with not setting correct published time for fetched posts.
- Fixed an issue with an open docker port on lemmy-ui.
- Using lemmy post link for RSS link.
- Fixed reason and display name lengths to use char counts instead.
### Lemmy-ui
- Updated translations.
- Made websocket host configurable.
- Added some accessibility features.
- Always showing password reset link.
# Lemmy v0.9.7 Release (2021-02-08)
## Changes
- Posts and comments are no longer live-sorted (meaning most content should stay in place).
- Fixed an issue with the create post title field not expanding when copied from iframely suggestion.
- Fixed broken federated community paging / sorting.
- Added aria attributes for accessibility, thx to @Mitch Lillie.
- Updated translations and added croatian.
- No changes to lemmy back-end.
# Lemmy v0.9.6 Release (2021-02-05)
## Changes
- Fixed inbox_urls not being correctly set, which broke federation in `v0.9.5`. Added some logging to catch these.
- Fixing community search not using auth.
- Moved docs to https://join.lemmy.ml
- Fixed an issue w/ lemmy-ui with forms being cleared out.
# Lemmy v0.9.4 Pre-Release (2021-02-02)
## Changes
### Lemmy
- Fixed a critical bug with votes and comment unlike responses not being `0` for your user.
- Fixed a critical bug with comment creation not checking if its parent comment is in the post.
- Serving proper activities for community outbox.
- Added some active user counts, including `users_active_day`, `users_active_week`, `users_active_month`, `users_active_half_year` to `SiteAggregates` and `CommunityAggregates`. (Also added to lemmy-ui)
- Made sure banned users can't follow.
- Added `FederatedInstances` to `SiteResponse`, to show allowed and blocked instances. (Also added to lemmy-ui)
- Added a `MostComments` sort for posts. (Also added to lemmy-ui)
### Lemmy-UI
- Added a scroll position restore to lemmy-ui.
- Reworked the combined inbox so incoming comments don't wipe out your current form.
- Fixed an updated bug on the user page.
- Fixed cross-post titles and body getting clipped.
- Fixing the post creation title height.
- Squashed some other smaller bugs.
# Lemmy v0.9.0 Release (2021-01-25) # Lemmy v0.9.0 Release (2021-01-25)
## Changes ## Changes
@ -116,10 +53,10 @@ None of these are breaking changes, so federation between 0.9.0 and 0.8.11 will
## Upgrading ## 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). If you'd like to make a DB backup before upgrading, follow [this guide](https://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 manual Docker installation](https://lemmy.ml/docs/en/administration/install_docker.html#updating)
- [Upgrade with Ansible installation](https://join.lemmy.ml/docs/en/administration/install_ansible.html) - [Upgrade with Ansible installation](https://lemmy.ml/docs/en/administration/install_ansible.html)
# Lemmy v0.8.0 Release (2020-10-16) # Lemmy v0.8.0 Release (2020-10-16)
@ -143,7 +80,7 @@ Here are some of the bigger changes:
- The first **federation public beta release**, woohoo :fireworks: - The first **federation public beta release**, woohoo :fireworks:
- All Lemmy functionality now works over ActivityPub (except turning remote users into mods/admins) - All Lemmy functionality now works over ActivityPub (except turning remote users into mods/admins)
- Instance allowlist and blocklist - Instance allowlist and blocklist
- Documentation for [admins](https://join.lemmy.ml/docs/administration_federation.html) and [devs](https://join.lemmy.ml/docs/contributing_federation_overview.html) on how federation works - Documentation for [admins](https://lemmy.ml/docs/administration_federation.html) and [devs](https://lemmy.ml/docs/contributing_federation_overview.html) on how federation works
- Upgraded to newest versions of @asonix activitypub libraries - Upgraded to newest versions of @asonix activitypub libraries
- Full local federation setup for manual testing - Full local federation setup for manual testing
- Automated testing for nearly every federation action - Automated testing for nearly every federation action
@ -181,8 +118,8 @@ We'd also like to thank both the [NLnet foundation](https://nlnet.nl/) for their
## Upgrading ## Upgrading
- [with manual Docker installation](https://join.lemmy.ml/docs/administration_install_docker.html#updating) - [with manual Docker installation](https://lemmy.ml/docs/administration_install_docker.html#updating)
- [with Ansible installation](https://join.lemmy.ml/docs/administration_install_ansible.html) - [with Ansible installation](https://lemmy.ml/docs/administration_install_ansible.html)
## Testing Federation ## Testing Federation
@ -270,7 +207,7 @@ Overall, since our last major release in January (v0.6.0), we have closed over
Before starting the upgrade, make sure that you have a working backup of your Before starting the upgrade, make sure that you have a working backup of your
database and image files. See our database and image files. See our
[documentation](https://join.lemmy.ml/docs/administration_backup_and_restore.html) [documentation](https://lemmy.ml/docs/administration_backup_and_restore.html)
for backup instructions. for backup instructions.
**With Ansible:** **With Ansible:**

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@
"eslint": "^7.18.0", "eslint": "^7.18.0",
"eslint-plugin-jane": "^9.0.3", "eslint-plugin-jane": "^9.0.3",
"jest": "^26.6.3", "jest": "^26.6.3",
"lemmy-js-client": "0.10.0-rc.4", "lemmy-js-client": "0.9.1-rc.1",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"prettier": "^2.1.2", "prettier": "^2.1.2",
"ts-jest": "^26.4.4", "ts-jest": "^26.4.4",

View file

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

View file

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

View file

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

View file

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

View file

@ -25,7 +25,7 @@ import {
CreateCommunity, CreateCommunity,
DeleteCommunity, DeleteCommunity,
RemoveCommunity, RemoveCommunity,
GetPersonMentions, GetUserMentions,
CreateCommentLike, CreateCommentLike,
CreatePostLike, CreatePostLike,
EditPrivateMessage, EditPrivateMessage,
@ -36,15 +36,15 @@ import {
GetPost, GetPost,
PrivateMessageResponse, PrivateMessageResponse,
PrivateMessagesResponse, PrivateMessagesResponse,
GetPersonMentionsResponse, GetUserMentionsResponse,
SaveUserSettings, SaveUserSettings,
SortType, SortType,
ListingType, ListingType,
GetSiteResponse, GetSiteResponse,
SearchType, SearchType,
LemmyHttp, LemmyHttp,
BanPersonResponse, BanUserResponse,
BanPerson, BanUser,
BanFromCommunity, BanFromCommunity,
BanFromCommunityResponse, BanFromCommunityResponse,
Post, Post,
@ -289,32 +289,32 @@ export async function searchForUser(
return api.client.search(form); return api.client.search(form);
} }
export async function banPersonFromSite( export async function banUserFromSite(
api: API, api: API,
person_id: number, user_id: number,
ban: boolean ban: boolean
): Promise<BanPersonResponse> { ): Promise<BanUserResponse> {
// Make sure lemmy-beta/c/main is cached on lemmy_alpha // Make sure lemmy-beta/c/main is cached on lemmy_alpha
// Use short-hand search url // Use short-hand search url
let form: BanPerson = { let form: BanUser = {
person_id, user_id,
ban, ban,
remove_data: false, remove_data: false,
auth: api.auth, auth: api.auth,
}; };
return api.client.banPerson(form); return api.client.banUser(form);
} }
export async function banPersonFromCommunity( export async function banUserFromCommunity(
api: API, api: API,
person_id: number, user_id: number,
community_id: number, community_id: number,
ban: boolean ban: boolean
): Promise<BanFromCommunityResponse> { ): Promise<BanFromCommunityResponse> {
// Make sure lemmy-beta/c/main is cached on lemmy_alpha // Make sure lemmy-beta/c/main is cached on lemmy_alpha
// Use short-hand search url // Use short-hand search url
let form: BanFromCommunity = { let form: BanFromCommunity = {
person_id, user_id,
community_id, community_id,
remove_data: false, remove_data: false,
ban, ban,
@ -413,13 +413,13 @@ export async function removeComment(
return api.client.removeComment(form); return api.client.removeComment(form);
} }
export async function getMentions(api: API): Promise<GetPersonMentionsResponse> { export async function getMentions(api: API): Promise<GetUserMentionsResponse> {
let form: GetPersonMentions = { let form: GetUserMentions = {
sort: SortType.New, sort: SortType.New,
unread_only: false, unread_only: false,
auth: api.auth, auth: api.auth,
}; };
return api.client.getPersonMentions(form); return api.client.getUserMentions(form);
} }
export async function likeComment( export async function likeComment(
@ -448,6 +448,7 @@ export async function createCommunity(
description, description,
icon, icon,
banner, banner,
category_id: 1,
nsfw: false, nsfw: false,
auth: api.auth, auth: api.auth,
}; };

View file

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

View file

@ -3233,10 +3233,10 @@ language-tags@^1.0.5:
dependencies: dependencies:
language-subtag-registry "~0.3.2" language-subtag-registry "~0.3.2"
lemmy-js-client@0.10.0-rc.4: lemmy-js-client@0.9.1-rc.1:
version "0.10.0-rc.4" version "0.9.1-rc.1"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.10.0-rc.4.tgz#ac6fe6940fc5f73260ddb166ce0ef3c0520901fc" resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.9.1-rc.1.tgz#afe3cb0d4852f849dd087a4756a3771bc920a907"
integrity sha512-yJPnvGaWneOOwjKEqb4qXtQk+4DbRgO+hEzSin2GgUgnxluY43gemwiCPt6EnV+j4ueKoi0+QORVg2RuRC2PaQ== integrity sha512-aVvo4IeJvIPUvypipk4GnyLB6nVQVLfB0arYrMkVV4L7zrZ/0pGtpkMDLaOAj/KpA6O0u9eLmaou5RberZQolA==
leven@^3.1.0: leven@^3.1.0:
version "3.1.0" version "3.1.0"

View file

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

View file

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

View file

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

View file

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

View file

@ -1,24 +1,16 @@
use crate::{ use crate::{
check_community_ban, check_optional_url,
get_local_user_view_from_jwt, get_user_from_jwt,
get_local_user_view_from_jwt_opt, get_user_from_jwt_opt,
is_admin, is_admin,
is_mod_or_admin, is_mod_or_admin,
Perform, Perform,
}; };
use actix_web::web::Data; use actix_web::web::Data;
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::{blocking, community::*}; use lemmy_apub::ActorType;
use lemmy_apub::{
generate_apub_endpoint,
generate_followers_url,
generate_inbox_url,
generate_shared_inbox_url,
ActorType,
EndpointType,
};
use lemmy_db_queries::{ use lemmy_db_queries::{
diesel_option_overwrite_to_url, diesel_option_overwrite,
source::{ source::{
comment::Comment_, comment::Comment_,
community::{CommunityModerator_, Community_}, community::{CommunityModerator_, Community_},
@ -35,20 +27,20 @@ use lemmy_db_queries::{
use lemmy_db_schema::{ use lemmy_db_schema::{
naive_now, naive_now,
source::{comment::Comment, community::*, moderator::*, post::Post, site::*}, source::{comment::Comment, community::*, moderator::*, post::Post, site::*},
PersonId,
}; };
use lemmy_db_views::comment_view::CommentQueryBuilder; use lemmy_db_views::comment_view::CommentQueryBuilder;
use lemmy_db_views_actor::{ use lemmy_db_views_actor::{
community_follower_view::CommunityFollowerView, community_follower_view::CommunityFollowerView,
community_moderator_view::CommunityModeratorView, community_moderator_view::CommunityModeratorView,
community_view::{CommunityQueryBuilder, CommunityView}, community_view::{CommunityQueryBuilder, CommunityView},
person_view::PersonViewSafe, user_view::UserViewSafe,
}; };
use lemmy_structs::{blocking, community::*};
use lemmy_utils::{ use lemmy_utils::{
apub::generate_actor_keypair, apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
location_info, location_info,
utils::{check_slurs, check_slurs_opt, is_valid_community_name, naive_from_unix}, utils::{check_slurs, check_slurs_opt, is_valid_community_name, naive_from_unix},
ApiError, APIError,
ConnectionId, ConnectionId,
LemmyError, LemmyError,
}; };
@ -69,8 +61,8 @@ impl Perform for GetCommunity {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetCommunityResponse, LemmyError> { ) -> Result<GetCommunityResponse, LemmyError> {
let data: &GetCommunity = &self; let data: &GetCommunity = &self;
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
let person_id = local_user_view.map(|u| u.person.id); let user_id = user.map(|u| u.id);
let community_id = match data.id { let community_id = match data.id {
Some(id) => id, Some(id) => id,
@ -82,19 +74,19 @@ impl Perform for GetCommunity {
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
} }
.id .id
} }
}; };
let community_view = match blocking(context.pool(), move |conn| { let community_view = match blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, person_id) CommunityView::read(conn, community_id, user_id)
}) })
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}; };
let moderators: Vec<CommunityModeratorView> = match blocking(context.pool(), move |conn| { let moderators: Vec<CommunityModeratorView> = match blocking(context.pool(), move |conn| {
@ -103,7 +95,7 @@ impl Perform for GetCommunity {
.await? .await?
{ {
Ok(moderators) => moderators, Ok(moderators) => moderators,
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}; };
let online = context let online = context
@ -133,30 +125,33 @@ impl Perform for CreateCommunity {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<CommunityResponse, LemmyError> {
let data: &CreateCommunity = &self; let data: &CreateCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
check_slurs(&data.name)?; check_slurs(&data.name)?;
check_slurs(&data.title)?; check_slurs(&data.title)?;
check_slurs_opt(&data.description)?; check_slurs_opt(&data.description)?;
if !is_valid_community_name(&data.name) { if !is_valid_community_name(&data.name) {
return Err(ApiError::err("invalid_community_name").into()); return Err(APIError::err("invalid_community_name").into());
} }
// Double check for duplicate community actor_ids // Double check for duplicate community actor_ids
let community_actor_id = generate_apub_endpoint(EndpointType::Community, &data.name)?; let actor_id = make_apub_endpoint(EndpointType::Community, &data.name);
let actor_id_cloned = community_actor_id.to_owned(); let actor_id_cloned = actor_id.to_owned();
let community_dupe = blocking(context.pool(), move |conn| { let community_dupe = blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &actor_id_cloned) Community::read_from_apub_id(conn, &actor_id_cloned.into())
}) })
.await?; .await?;
if community_dupe.is_ok() { if community_dupe.is_ok() {
return Err(ApiError::err("community_already_exists").into()); return Err(APIError::err("community_already_exists").into());
} }
// Check to make sure the icon and banners are urls // Check to make sure the icon and banners are urls
let icon = diesel_option_overwrite_to_url(&data.icon)?; let icon = diesel_option_overwrite(&data.icon);
let banner = diesel_option_overwrite_to_url(&data.banner)?; let banner = diesel_option_overwrite(&data.banner);
check_optional_url(&icon)?;
check_optional_url(&banner)?;
// When you create a community, make sure the user becomes a moderator and a follower // When you create a community, make sure the user becomes a moderator and a follower
let keypair = generate_actor_keypair()?; let keypair = generate_actor_keypair()?;
@ -167,20 +162,18 @@ impl Perform for CreateCommunity {
description: data.description.to_owned(), description: data.description.to_owned(),
icon, icon,
banner, banner,
creator_id: local_user_view.person.id, category_id: data.category_id,
creator_id: user.id,
removed: None, removed: None,
deleted: None, deleted: None,
nsfw: data.nsfw, nsfw: data.nsfw,
updated: None, updated: None,
actor_id: Some(community_actor_id.to_owned()), actor_id: Some(actor_id.into()),
local: true, local: true,
private_key: Some(keypair.private_key), private_key: Some(keypair.private_key),
public_key: Some(keypair.public_key), public_key: Some(keypair.public_key),
last_refreshed_at: None, last_refreshed_at: None,
published: None, published: None,
followers_url: Some(generate_followers_url(&community_actor_id)?),
inbox_url: Some(generate_inbox_url(&community_actor_id)?),
shared_inbox_url: Some(Some(generate_shared_inbox_url(&community_actor_id)?)),
}; };
let inserted_community = match blocking(context.pool(), move |conn| { let inserted_community = match blocking(context.pool(), move |conn| {
@ -189,35 +182,35 @@ impl Perform for CreateCommunity {
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("community_already_exists").into()), Err(_e) => return Err(APIError::err("community_already_exists").into()),
}; };
// The community creator becomes a moderator // The community creator becomes a moderator
let community_moderator_form = CommunityModeratorForm { let community_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: local_user_view.person.id, user_id: user.id,
}; };
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
if blocking(context.pool(), join).await?.is_err() { if blocking(context.pool(), join).await?.is_err() {
return Err(ApiError::err("community_moderator_already_exists").into()); return Err(APIError::err("community_moderator_already_exists").into());
} }
// Follow your own community // Follow your own community
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: local_user_view.person.id, user_id: user.id,
pending: false, pending: false,
}; };
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form); let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
if blocking(context.pool(), follow).await?.is_err() { if blocking(context.pool(), follow).await?.is_err() {
return Err(ApiError::err("community_follower_already_exists").into()); return Err(APIError::err("community_follower_already_exists").into());
} }
let person_id = local_user_view.person.id; let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| { let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, inserted_community.id, Some(person_id)) CommunityView::read(conn, inserted_community.id, Some(user_id))
}) })
.await??; .await??;
@ -235,20 +228,20 @@ impl Perform for EditCommunity {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<CommunityResponse, LemmyError> {
let data: &EditCommunity = &self; let data: &EditCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
check_slurs(&data.title)?; check_slurs(&data.title)?;
check_slurs_opt(&data.description)?; check_slurs_opt(&data.description)?;
// Verify its a mod (only mods can edit it) // Verify its a mod (only mods can edit it)
let community_id = data.community_id; let community_id = data.community_id;
let mods: Vec<PersonId> = blocking(context.pool(), move |conn| { let mods: Vec<i32> = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id) CommunityModeratorView::for_community(conn, community_id)
.map(|v| v.into_iter().map(|m| m.moderator.id).collect()) .map(|v| v.into_iter().map(|m| m.moderator.id).collect())
}) })
.await??; .await??;
if !mods.contains(&local_user_view.person.id) { if !mods.contains(&user.id) {
return Err(ApiError::err("not_a_moderator").into()); return Err(APIError::err("not_a_moderator").into());
} }
let community_id = data.community_id; let community_id = data.community_id;
@ -257,8 +250,11 @@ impl Perform for EditCommunity {
}) })
.await??; .await??;
let icon = diesel_option_overwrite_to_url(&data.icon)?; let icon = diesel_option_overwrite(&data.icon);
let banner = diesel_option_overwrite_to_url(&data.banner)?; let banner = diesel_option_overwrite(&data.banner);
check_optional_url(&icon)?;
check_optional_url(&banner)?;
let community_form = CommunityForm { let community_form = CommunityForm {
name: read_community.name, name: read_community.name,
@ -266,6 +262,7 @@ impl Perform for EditCommunity {
description: data.description.to_owned(), description: data.description.to_owned(),
icon, icon,
banner, banner,
category_id: data.category_id.to_owned(),
creator_id: read_community.creator_id, creator_id: read_community.creator_id,
removed: Some(read_community.removed), removed: Some(read_community.removed),
deleted: Some(read_community.deleted), deleted: Some(read_community.deleted),
@ -277,9 +274,6 @@ impl Perform for EditCommunity {
public_key: read_community.public_key, public_key: read_community.public_key,
last_refreshed_at: None, last_refreshed_at: None,
published: None, published: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let community_id = data.community_id; let community_id = data.community_id;
@ -289,16 +283,16 @@ impl Perform for EditCommunity {
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()), Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
}; };
// TODO there needs to be some kind of an apub update // TODO there needs to be some kind of an apub update
// process for communities and users // process for communities and users
let community_id = data.community_id; let community_id = data.community_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| { let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(person_id)) CommunityView::read(conn, community_id, Some(user_id))
}) })
.await??; .await??;
@ -320,7 +314,7 @@ impl Perform for DeleteCommunity {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<CommunityResponse, LemmyError> {
let data: &DeleteCommunity = &self; let data: &DeleteCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Verify its the creator (only a creator can delete the community) // Verify its the creator (only a creator can delete the community)
let community_id = data.community_id; let community_id = data.community_id;
@ -328,8 +322,8 @@ impl Perform for DeleteCommunity {
Community::read(conn, community_id) Community::read(conn, community_id)
}) })
.await??; .await??;
if read_community.creator_id != local_user_view.person.id { if read_community.creator_id != user.id {
return Err(ApiError::err("no_community_edit_allowed").into()); return Err(APIError::err("no_community_edit_allowed").into());
} }
// Do the delete // Do the delete
@ -341,7 +335,7 @@ impl Perform for DeleteCommunity {
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()), Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
}; };
// Send apub messages // Send apub messages
@ -352,9 +346,9 @@ impl Perform for DeleteCommunity {
} }
let community_id = data.community_id; let community_id = data.community_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| { let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(person_id)) CommunityView::read(conn, community_id, Some(user_id))
}) })
.await??; .await??;
@ -376,10 +370,10 @@ impl Perform for RemoveCommunity {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<CommunityResponse, LemmyError> {
let data: &RemoveCommunity = &self; let data: &RemoveCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
// Verify its an admin (only an admin can remove a community) // Verify its an admin (only an admin can remove a community)
is_admin(&local_user_view)?; is_admin(context.pool(), user.id).await?;
// Do the remove // Do the remove
let community_id = data.community_id; let community_id = data.community_id;
@ -390,7 +384,7 @@ impl Perform for RemoveCommunity {
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_update_community").into()), Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
}; };
// Mod tables // Mod tables
@ -399,7 +393,7 @@ impl Perform for RemoveCommunity {
None => None, None => None,
}; };
let form = ModRemoveCommunityForm { let form = ModRemoveCommunityForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
community_id: data.community_id, community_id: data.community_id,
removed: Some(removed), removed: Some(removed),
reason: data.reason.to_owned(), reason: data.reason.to_owned(),
@ -418,9 +412,9 @@ impl Perform for RemoveCommunity {
} }
let community_id = data.community_id; let community_id = data.community_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let community_view = blocking(context.pool(), move |conn| { let community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(person_id)) CommunityView::read(conn, community_id, Some(user_id))
}) })
.await??; .await??;
@ -442,16 +436,15 @@ impl Perform for ListCommunities {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<ListCommunitiesResponse, LemmyError> { ) -> Result<ListCommunitiesResponse, LemmyError> {
let data: &ListCommunities = &self; let data: &ListCommunities = &self;
let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?; let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
let person_id = match &local_user_view { let user_id = match &user {
Some(uv) => Some(uv.person.id), Some(user) => Some(user.id),
None => None, None => None,
}; };
// Don't show NSFW by default let show_nsfw = match &user {
let show_nsfw = match &local_user_view { Some(user) => user.show_nsfw,
Some(uv) => uv.local_user.show_nsfw,
None => false, None => false,
}; };
@ -465,7 +458,7 @@ impl Perform for ListCommunities {
.listing_type(&type_) .listing_type(&type_)
.sort(&sort) .sort(&sort)
.show_nsfw(show_nsfw) .show_nsfw(show_nsfw)
.my_person_id(person_id) .my_user_id(user_id)
.page(page) .page(page)
.limit(limit) .limit(limit)
.list() .list()
@ -487,7 +480,7 @@ impl Perform for FollowCommunity {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<CommunityResponse, LemmyError> {
let data: &FollowCommunity = &self; let data: &FollowCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id; let community_id = data.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
@ -496,47 +489,39 @@ impl Perform for FollowCommunity {
.await??; .await??;
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: data.community_id, community_id: data.community_id,
person_id: local_user_view.person.id, user_id: user.id,
pending: false, pending: false,
}; };
if community.local { if community.local {
if data.follow { if data.follow {
check_community_ban(local_user_view.person.id, community_id, context.pool()).await?;
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form); let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
if blocking(context.pool(), follow).await?.is_err() { if blocking(context.pool(), follow).await?.is_err() {
return Err(ApiError::err("community_follower_already_exists").into()); return Err(APIError::err("community_follower_already_exists").into());
} }
} else { } else {
let unfollow = let unfollow =
move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form); move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
if blocking(context.pool(), unfollow).await?.is_err() { if blocking(context.pool(), unfollow).await?.is_err() {
return Err(ApiError::err("community_follower_already_exists").into()); return Err(APIError::err("community_follower_already_exists").into());
} }
} }
} else if data.follow { } else if data.follow {
// Dont actually add to the community followers here, because you need // Dont actually add to the community followers here, because you need
// to wait for the accept // to wait for the accept
local_user_view user.send_follow(&community.actor_id(), context).await?;
.person
.send_follow(&community.actor_id(), context)
.await?;
} else { } else {
local_user_view user.send_unfollow(&community.actor_id(), context).await?;
.person
.send_unfollow(&community.actor_id(), context)
.await?;
let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form); let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
if blocking(context.pool(), unfollow).await?.is_err() { if blocking(context.pool(), unfollow).await?.is_err() {
return Err(ApiError::err("community_follower_already_exists").into()); return Err(APIError::err("community_follower_already_exists").into());
} }
} }
let community_id = data.community_id; let community_id = data.community_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let mut community_view = blocking(context.pool(), move |conn| { let mut community_view = blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(person_id)) CommunityView::read(conn, community_id, Some(user_id))
}) })
.await??; .await??;
@ -561,16 +546,16 @@ impl Perform for GetFollowedCommunities {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetFollowedCommunitiesResponse, LemmyError> { ) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
let data: &GetFollowedCommunities = &self; let data: &GetFollowedCommunities = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let person_id = local_user_view.person.id; let user_id = user.id;
let communities = match blocking(context.pool(), move |conn| { let communities = match blocking(context.pool(), move |conn| {
CommunityFollowerView::for_person(conn, person_id) CommunityFollowerView::for_user(conn, user_id)
}) })
.await? .await?
{ {
Ok(communities) => communities, Ok(communities) => communities,
_ => return Err(ApiError::err("system_err_login").into()), _ => return Err(APIError::err("system_err_login").into()),
}; };
// Return the jwt // Return the jwt
@ -588,40 +573,28 @@ impl Perform for BanFromCommunity {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<BanFromCommunityResponse, LemmyError> { ) -> Result<BanFromCommunityResponse, LemmyError> {
let data: &BanFromCommunity = &self; let data: &BanFromCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id; let community_id = data.community_id;
let banned_person_id = data.person_id; let banned_user_id = data.user_id;
// Verify that only mods or admins can ban // Verify that only mods or admins can ban
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?; is_mod_or_admin(context.pool(), user.id, community_id).await?;
let community_user_ban_form = CommunityPersonBanForm { let community_user_ban_form = CommunityUserBanForm {
community_id: data.community_id, community_id: data.community_id,
person_id: data.person_id, user_id: data.user_id,
}; };
if data.ban { if data.ban {
let ban = move |conn: &'_ _| CommunityPersonBan::ban(conn, &community_user_ban_form); let ban = move |conn: &'_ _| CommunityUserBan::ban(conn, &community_user_ban_form);
if blocking(context.pool(), ban).await?.is_err() { if blocking(context.pool(), ban).await?.is_err() {
return Err(ApiError::err("community_user_already_banned").into()); return Err(APIError::err("community_user_already_banned").into());
} }
// Also unsubscribe them from the community, if they are subscribed
let community_follower_form = CommunityFollowerForm {
community_id: data.community_id,
person_id: banned_person_id,
pending: false,
};
blocking(context.pool(), move |conn: &'_ _| {
CommunityFollower::unfollow(conn, &community_follower_form)
})
.await?
.ok();
} else { } else {
let unban = move |conn: &'_ _| CommunityPersonBan::unban(conn, &community_user_ban_form); let unban = move |conn: &'_ _| CommunityUserBan::unban(conn, &community_user_ban_form);
if blocking(context.pool(), unban).await?.is_err() { if blocking(context.pool(), unban).await?.is_err() {
return Err(ApiError::err("community_user_already_banned").into()); return Err(APIError::err("community_user_already_banned").into());
} }
} }
@ -629,7 +602,7 @@ impl Perform for BanFromCommunity {
if data.remove_data { if data.remove_data {
// Posts // Posts
blocking(context.pool(), move |conn: &'_ _| { blocking(context.pool(), move |conn: &'_ _| {
Post::update_removed_for_creator(conn, banned_person_id, Some(community_id), true) Post::update_removed_for_creator(conn, banned_user_id, Some(community_id), true)
}) })
.await??; .await??;
@ -637,7 +610,7 @@ impl Perform for BanFromCommunity {
// TODO Diesel doesn't allow updates with joins, so this has to be a loop // TODO Diesel doesn't allow updates with joins, so this has to be a loop
let comments = blocking(context.pool(), move |conn| { let comments = blocking(context.pool(), move |conn| {
CommentQueryBuilder::create(conn) CommentQueryBuilder::create(conn)
.creator_id(banned_person_id) .creator_id(banned_user_id)
.community_id(community_id) .community_id(community_id)
.limit(std::i64::MAX) .limit(std::i64::MAX)
.list() .list()
@ -661,8 +634,8 @@ impl Perform for BanFromCommunity {
}; };
let form = ModBanFromCommunityForm { let form = ModBanFromCommunityForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
other_person_id: data.person_id, other_user_id: data.user_id,
community_id: data.community_id, community_id: data.community_id,
reason: data.reason.to_owned(), reason: data.reason.to_owned(),
banned: Some(data.ban), banned: Some(data.ban),
@ -673,14 +646,14 @@ impl Perform for BanFromCommunity {
}) })
.await??; .await??;
let person_id = data.person_id; let user_id = data.user_id;
let person_view = blocking(context.pool(), move |conn| { let user_view = blocking(context.pool(), move |conn| {
PersonViewSafe::read(conn, person_id) UserViewSafe::read(conn, user_id)
}) })
.await??; .await??;
let res = BanFromCommunityResponse { let res = BanFromCommunityResponse {
person_view, user_view,
banned: data.ban, banned: data.ban,
}; };
@ -705,34 +678,34 @@ impl Perform for AddModToCommunity {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<AddModToCommunityResponse, LemmyError> { ) -> Result<AddModToCommunityResponse, LemmyError> {
let data: &AddModToCommunity = &self; let data: &AddModToCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_moderator_form = CommunityModeratorForm { let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id, community_id: data.community_id,
person_id: data.person_id, user_id: data.user_id,
}; };
let community_id = data.community_id; let community_id = data.community_id;
// Verify that only mods or admins can add mod // Verify that only mods or admins can add mod
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?; is_mod_or_admin(context.pool(), user.id, community_id).await?;
if data.added { if data.added {
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
if blocking(context.pool(), join).await?.is_err() { if blocking(context.pool(), join).await?.is_err() {
return Err(ApiError::err("community_moderator_already_exists").into()); return Err(APIError::err("community_moderator_already_exists").into());
} }
} else { } else {
let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form); let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
if blocking(context.pool(), leave).await?.is_err() { if blocking(context.pool(), leave).await?.is_err() {
return Err(ApiError::err("community_moderator_already_exists").into()); return Err(APIError::err("community_moderator_already_exists").into());
} }
} }
// Mod tables // Mod tables
let form = ModAddCommunityForm { let form = ModAddCommunityForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
other_person_id: data.person_id, other_user_id: data.user_id,
community_id: data.community_id, community_id: data.community_id,
removed: Some(!data.added), removed: Some(!data.added),
}; };
@ -770,7 +743,7 @@ impl Perform for TransferCommunity {
_websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetCommunityResponse, LemmyError> { ) -> Result<GetCommunityResponse, LemmyError> {
let data: &TransferCommunity = &self; let data: &TransferCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id; let community_id = data.community_id;
let read_community = blocking(context.pool(), move |conn| { let read_community = blocking(context.pool(), move |conn| {
@ -783,31 +756,28 @@ impl Perform for TransferCommunity {
}) })
.await??; .await??;
let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??; let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
// Making sure the creator, if an admin, is at the top // Making sure the creator, if an admin, is at the top
let creator_index = admins let creator_index = admins
.iter() .iter()
.position(|r| r.person.id == site_creator_id) .position(|r| r.user.id == site_creator_id)
.context(location_info!())?; .context(location_info!())?;
let creator_person = admins.remove(creator_index); let creator_user = admins.remove(creator_index);
admins.insert(0, creator_person); admins.insert(0, creator_user);
// Make sure user is the creator, or an admin // Make sure user is the creator, or an admin
if local_user_view.person.id != read_community.creator_id if user.id != read_community.creator_id
&& !admins && !admins.iter().map(|a| a.user.id).any(|x| x == user.id)
.iter()
.map(|a| a.person.id)
.any(|x| x == local_user_view.person.id)
{ {
return Err(ApiError::err("not_an_admin").into()); return Err(APIError::err("not_an_admin").into());
} }
let community_id = data.community_id; let community_id = data.community_id;
let new_creator = data.person_id; let new_creator = data.user_id;
let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator); let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
if blocking(context.pool(), update).await?.is_err() { if blocking(context.pool(), update).await?.is_err() {
return Err(ApiError::err("couldnt_update_community").into()); return Err(APIError::err("couldnt_update_community").into());
}; };
// You also have to re-do the community_moderator table, reordering it. // You also have to re-do the community_moderator table, reordering it.
@ -818,10 +788,10 @@ impl Perform for TransferCommunity {
.await??; .await??;
let creator_index = community_mods let creator_index = community_mods
.iter() .iter()
.position(|r| r.moderator.id == data.person_id) .position(|r| r.moderator.id == data.user_id)
.context(location_info!())?; .context(location_info!())?;
let creator_person = community_mods.remove(creator_index); let creator_user = community_mods.remove(creator_index);
community_mods.insert(0, creator_person); community_mods.insert(0, creator_user);
let community_id = data.community_id; let community_id = data.community_id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
@ -833,19 +803,19 @@ impl Perform for TransferCommunity {
for cmod in &community_mods { for cmod in &community_mods {
let community_moderator_form = CommunityModeratorForm { let community_moderator_form = CommunityModeratorForm {
community_id: cmod.community.id, community_id: cmod.community.id,
person_id: cmod.moderator.id, user_id: cmod.moderator.id,
}; };
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
if blocking(context.pool(), join).await?.is_err() { if blocking(context.pool(), join).await?.is_err() {
return Err(ApiError::err("community_moderator_already_exists").into()); return Err(APIError::err("community_moderator_already_exists").into());
} }
} }
// Mod tables // Mod tables
let form = ModAddCommunityForm { let form = ModAddCommunityForm {
mod_person_id: local_user_view.person.id, mod_user_id: user.id,
other_person_id: data.person_id, other_user_id: data.user_id,
community_id: data.community_id, community_id: data.community_id,
removed: Some(false), removed: Some(false),
}; };
@ -855,14 +825,14 @@ impl Perform for TransferCommunity {
.await??; .await??;
let community_id = data.community_id; let community_id = data.community_id;
let person_id = local_user_view.person.id; let user_id = user.id;
let community_view = match blocking(context.pool(), move |conn| { let community_view = match blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, Some(person_id)) CommunityView::read(conn, community_id, Some(user_id))
}) })
.await? .await?
{ {
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}; };
let community_id = data.community_id; let community_id = data.community_id;
@ -872,7 +842,7 @@ impl Perform for TransferCommunity {
.await? .await?
{ {
Ok(moderators) => moderators, Ok(moderators) => moderators,
Err(_e) => return Err(ApiError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}; };
// Return the jwt // Return the jwt
@ -890,7 +860,7 @@ fn send_community_websocket(
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
op: UserOperation, op: UserOperation,
) { ) {
// Strip out the person id and subscribed when sending to others // Strip out the user id and subscribed when sending to others
let mut res_sent = res.clone(); let mut res_sent = res.clone();
res_sent.community_view.subscribed = false; res_sent.community_view.subscribed = false;

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,6 +1,6 @@
use crate::{get_local_user_view_from_jwt, Perform}; use crate::{get_user_from_jwt, Perform};
use actix_web::web::Data; use actix_web::web::Data;
use lemmy_api_structs::websocket::*; use lemmy_structs::websocket::*;
use lemmy_utils::{ConnectionId, LemmyError}; use lemmy_utils::{ConnectionId, LemmyError};
use lemmy_websocket::{ use lemmy_websocket::{
messages::{JoinCommunityRoom, JoinModRoom, JoinPostRoom, JoinUserRoom}, messages::{JoinCommunityRoom, JoinModRoom, JoinPostRoom, JoinUserRoom},
@ -17,11 +17,11 @@ impl Perform for UserJoin {
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<UserJoinResponse, LemmyError> { ) -> Result<UserJoinResponse, LemmyError> {
let data: &UserJoin = &self; let data: &UserJoin = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
if let Some(ws_id) = websocket_id { if let Some(ws_id) = websocket_id {
context.chat_server().do_send(JoinUserRoom { context.chat_server().do_send(JoinUserRoom {
local_user_id: local_user_view.local_user.id, user_id: user.id,
id: ws_id, id: ws_id,
}); });
} }

View file

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

View file

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

View file

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

View file

@ -4,10 +4,10 @@ use activitystreams::{
base::{AnyBase, ExtendsExt}, base::{AnyBase, ExtendsExt},
}; };
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::{blocking, community::CommunityResponse};
use lemmy_db_queries::{source::community::Community_, ApubObject}; use lemmy_db_queries::{source::community::Community_, ApubObject};
use lemmy_db_schema::source::community::Community; use lemmy_db_schema::source::community::Community;
use lemmy_db_views_actor::community_view::CommunityView; use lemmy_db_views_actor::community_view::CommunityView;
use lemmy_structs::{blocking, community::CommunityResponse};
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation}; use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
use url::Url; use url::Url;

View file

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

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@ use crate::{
activities::send::generate_activity_id, activities::send::generate_activity_id,
activity_queue::{send_comment_mentions, send_to_community}, activity_queue::{send_comment_mentions, send_to_community},
extensions::context::lemmy_context, extensions::context::lemmy_context,
fetcher::person::get_or_fetch_and_upsert_person, fetcher::user::get_or_fetch_and_upsert_user,
objects::ToApub, objects::ToApub,
ActorType, ActorType,
ApubLikeableType, ApubLikeableType,
@ -26,12 +26,12 @@ use activitystreams::{
}; };
use anyhow::anyhow; use anyhow::anyhow;
use itertools::Itertools; use itertools::Itertools;
use lemmy_api_structs::{blocking, WebFingerResponse};
use lemmy_db_queries::{Crud, DbPool}; use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post}; use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post, user::User_};
use lemmy_structs::{blocking, WebFingerResponse};
use lemmy_utils::{ use lemmy_utils::{
request::{retry, RecvError}, request::{retry, RecvError},
settings::structs::Settings, settings::Settings,
utils::{scrape_text_for_mentions, MentionData}, utils::{scrape_text_for_mentions, MentionData},
LemmyError, LemmyError,
}; };
@ -44,8 +44,8 @@ use url::Url;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ApubObjectType for Comment { impl ApubObjectType for Comment {
/// Send out information about a newly created comment, to the followers of the community and /// Send out information about a newly created comment, to the followers of the community and
/// mentioned persons. /// mentioned users.
async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?; let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id; let post_id = self.post_id;
@ -77,8 +77,8 @@ impl ApubObjectType for Comment {
} }
/// Send out information about an edited post, to the followers of the community and mentioned /// Send out information about an edited post, to the followers of the community and mentioned
/// persons. /// users.
async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?; let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id; let post_id = self.post_id;
@ -109,7 +109,7 @@ impl ApubObjectType for Comment {
Ok(()) Ok(())
} }
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -135,7 +135,7 @@ impl ApubObjectType for Comment {
async fn send_undo_delete( async fn send_undo_delete(
&self, &self,
creator: &Person, creator: &User_,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
@ -173,7 +173,7 @@ impl ApubObjectType for Comment {
Ok(()) Ok(())
} }
async fn send_remove(&self, mod_: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -197,11 +197,7 @@ impl ApubObjectType for Comment {
Ok(()) Ok(())
} }
async fn send_undo_remove( async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
&self,
mod_: &Person,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -240,7 +236,7 @@ impl ApubObjectType for Comment {
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ApubLikeableType for Comment { impl ApubLikeableType for Comment {
async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -264,7 +260,7 @@ impl ApubLikeableType for Comment {
Ok(()) Ok(())
} }
async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -290,7 +286,7 @@ impl ApubLikeableType for Comment {
async fn send_undo_like( async fn send_undo_like(
&self, &self,
creator: &Person, creator: &User_,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let post_id = self.post_id; let post_id = self.post_id;
@ -346,7 +342,7 @@ impl MentionsAndAddresses {
/// This takes a comment, and builds a list of to_addresses, inboxes, /// This takes a comment, and builds a list of to_addresses, inboxes,
/// and mention tags, so they know where to be sent to. /// and mention tags, so they know where to be sent to.
/// Addresses are the persons / addresses that go in the cc field. /// Addresses are the users / addresses that go in the cc field.
async fn collect_non_local_mentions( async fn collect_non_local_mentions(
comment: &Comment, comment: &Comment,
community: &Community, community: &Community,
@ -355,12 +351,12 @@ async fn collect_non_local_mentions(
let parent_creator = get_comment_parent_creator(context.pool(), comment).await?; 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()` // Note: dont include community inbox here, as we send to it separately with `send_to_community()`
let mut inboxes = vec![parent_creator.get_shared_inbox_or_inbox_url()]; let mut inboxes = vec![parent_creator.get_shared_inbox_url()?];
// Add the mention tag // Add the mention tag
let mut tags = Vec::new(); let mut tags = Vec::new();
// Get the person IDs for any mentions // Get the user IDs for any mentions
let mentions = scrape_text_for_mentions(&comment.content) let mentions = scrape_text_for_mentions(&comment.content)
.into_iter() .into_iter()
// Filter only the non-local ones // Filter only the non-local ones
@ -373,8 +369,8 @@ async fn collect_non_local_mentions(
debug!("mention actor_id: {}", actor_id); debug!("mention actor_id: {}", actor_id);
addressed_ccs.push(actor_id.to_owned().to_string().parse()?); addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
let mention_person = get_or_fetch_and_upsert_person(&actor_id, context, &mut 0).await?; let mention_user = get_or_fetch_and_upsert_user(&actor_id, context, &mut 0).await?;
inboxes.push(mention_person.get_shared_inbox_or_inbox_url()); inboxes.push(mention_user.get_shared_inbox_url()?);
let mut mention_tag = Mention::new(); let mut mention_tag = Mention::new();
mention_tag.set_href(actor_id).set_name(mention.full_name()); mention_tag.set_href(actor_id).set_name(mention.full_name());
@ -391,12 +387,9 @@ async fn collect_non_local_mentions(
}) })
} }
/// Returns the apub ID of the person this comment is responding to. Meaning, in case this is a /// Returns the apub ID of the user this comment is responding to. Meaning, in case this is a
/// top-level comment, the creator of the post, otherwise the creator of the parent comment. /// top-level comment, the creator of the post, otherwise the creator of the parent comment.
async fn get_comment_parent_creator( async fn get_comment_parent_creator(pool: &DbPool, comment: &Comment) -> Result<User_, LemmyError> {
pool: &DbPool,
comment: &Comment,
) -> Result<Person, LemmyError> {
let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id { let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
let parent_comment = let parent_comment =
blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??; blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
@ -406,10 +399,10 @@ async fn get_comment_parent_creator(
let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??; let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
parent_post.creator_id parent_post.creator_id
}; };
Ok(blocking(pool, move |conn| Person::read(conn, parent_creator_id)).await??) Ok(blocking(pool, move |conn| User_::read(conn, parent_creator_id)).await??)
} }
/// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`, /// Turns a user id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
/// using webfinger. /// using webfinger.
async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> { async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
let fetch_url = format!( let fetch_url = format!(

View file

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

View file

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

View file

@ -21,16 +21,16 @@ use activitystreams::{
prelude::*, prelude::*,
public, public,
}; };
use lemmy_api_structs::blocking;
use lemmy_db_queries::Crud; use lemmy_db_queries::Crud;
use lemmy_db_schema::source::{community::Community, person::Person, post::Post}; use lemmy_db_schema::source::{community::Community, post::Post, user::User_};
use lemmy_structs::blocking;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ApubObjectType for Post { impl ApubObjectType for Post {
/// Send out information about a newly created post, to the followers of the community. /// Send out information about a newly created post, to the followers of the community.
async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let page = self.to_apub(context.pool()).await?; let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id; let community_id = self.community_id;
@ -54,7 +54,7 @@ impl ApubObjectType for Post {
} }
/// Send out information about an edited post, to the followers of the community. /// Send out information about an edited post, to the followers of the community.
async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let page = self.to_apub(context.pool()).await?; let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id; let community_id = self.community_id;
@ -77,7 +77,7 @@ impl ApubObjectType for Post {
Ok(()) Ok(())
} }
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id) Community::read(conn, community_id)
@ -100,7 +100,7 @@ impl ApubObjectType for Post {
async fn send_undo_delete( async fn send_undo_delete(
&self, &self,
creator: &Person, creator: &User_,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
@ -134,7 +134,7 @@ impl ApubObjectType for Post {
Ok(()) Ok(())
} }
async fn send_remove(&self, mod_: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id) Community::read(conn, community_id)
@ -155,11 +155,7 @@ impl ApubObjectType for Post {
Ok(()) Ok(())
} }
async fn send_undo_remove( async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
&self,
mod_: &Person,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id) Community::read(conn, community_id)
@ -194,7 +190,7 @@ impl ApubObjectType for Post {
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ApubLikeableType for Post { impl ApubLikeableType for Post {
async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id) Community::read(conn, community_id)
@ -215,7 +211,7 @@ impl ApubLikeableType for Post {
Ok(()) Ok(())
} }
async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id) Community::read(conn, community_id)
@ -238,7 +234,7 @@ impl ApubLikeableType for Post {
async fn send_undo_like( async fn send_undo_like(
&self, &self,
creator: &Person, creator: &User_,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let community_id = self.community_id; let community_id = self.community_id;

View file

@ -16,21 +16,20 @@ use activitystreams::{
}, },
prelude::*, prelude::*,
}; };
use lemmy_api_structs::blocking;
use lemmy_db_queries::Crud; use lemmy_db_queries::Crud;
use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage}; use lemmy_db_schema::source::{private_message::PrivateMessage, user::User_};
use lemmy_structs::blocking;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ApubObjectType for PrivateMessage { impl ApubObjectType for PrivateMessage {
/// Send out information about a newly created private message /// Send out information about a newly created private message
async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?; let note = self.to_apub(context.pool()).await?;
let recipient_id = self.recipient_id; let recipient_id = self.recipient_id;
let recipient = let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
let mut create = Create::new( let mut create = Create::new(
creator.actor_id.to_owned().into_inner(), creator.actor_id.to_owned().into_inner(),
@ -42,17 +41,16 @@ impl ApubObjectType for PrivateMessage {
.set_id(generate_activity_id(CreateType::Create)?) .set_id(generate_activity_id(CreateType::Create)?)
.set_to(recipient.actor_id()); .set_to(recipient.actor_id());
send_activity_single_dest(create, creator, recipient.inbox_url.into(), context).await?; send_activity_single_dest(create, creator, recipient.get_inbox_url()?, context).await?;
Ok(()) Ok(())
} }
/// Send out information about an edited private message, to the followers of the community. /// Send out information about an edited private message, to the followers of the community.
async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?; let note = self.to_apub(context.pool()).await?;
let recipient_id = self.recipient_id; let recipient_id = self.recipient_id;
let recipient = let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
let mut update = Update::new( let mut update = Update::new(
creator.actor_id.to_owned().into_inner(), creator.actor_id.to_owned().into_inner(),
@ -63,14 +61,13 @@ impl ApubObjectType for PrivateMessage {
.set_id(generate_activity_id(UpdateType::Update)?) .set_id(generate_activity_id(UpdateType::Update)?)
.set_to(recipient.actor_id()); .set_to(recipient.actor_id());
send_activity_single_dest(update, creator, recipient.inbox_url.into(), context).await?; send_activity_single_dest(update, creator, recipient.get_inbox_url()?, context).await?;
Ok(()) Ok(())
} }
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let recipient_id = self.recipient_id; let recipient_id = self.recipient_id;
let recipient = let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
let mut delete = Delete::new( let mut delete = Delete::new(
creator.actor_id.to_owned().into_inner(), creator.actor_id.to_owned().into_inner(),
@ -81,18 +78,17 @@ impl ApubObjectType for PrivateMessage {
.set_id(generate_activity_id(DeleteType::Delete)?) .set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(recipient.actor_id()); .set_to(recipient.actor_id());
send_activity_single_dest(delete, creator, recipient.inbox_url.into(), context).await?; send_activity_single_dest(delete, creator, recipient.get_inbox_url()?, context).await?;
Ok(()) Ok(())
} }
async fn send_undo_delete( async fn send_undo_delete(
&self, &self,
creator: &Person, creator: &User_,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let recipient_id = self.recipient_id; let recipient_id = self.recipient_id;
let recipient = let recipient = blocking(context.pool(), move |conn| User_::read(conn, recipient_id)).await??;
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
let mut delete = Delete::new( let mut delete = Delete::new(
creator.actor_id.to_owned().into_inner(), creator.actor_id.to_owned().into_inner(),
@ -113,17 +109,17 @@ impl ApubObjectType for PrivateMessage {
.set_id(generate_activity_id(UndoType::Undo)?) .set_id(generate_activity_id(UndoType::Undo)?)
.set_to(recipient.actor_id()); .set_to(recipient.actor_id());
send_activity_single_dest(undo, creator, recipient.inbox_url.into(), context).await?; send_activity_single_dest(undo, creator, recipient.get_inbox_url()?, context).await?;
Ok(()) Ok(())
} }
async fn send_remove(&self, _mod_: &Person, _context: &LemmyContext) -> Result<(), LemmyError> { async fn send_remove(&self, _mod_: &User_, _context: &LemmyContext) -> Result<(), LemmyError> {
unimplemented!() unimplemented!()
} }
async fn send_undo_remove( async fn send_undo_remove(
&self, &self,
_mod_: &Person, _mod_: &User_,
_context: &LemmyContext, _context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
unimplemented!() unimplemented!()

View file

@ -13,21 +13,18 @@ use activitystreams::{
base::{AnyBase, BaseExt, ExtendsExt}, base::{AnyBase, BaseExt, ExtendsExt},
object::ObjectExt, object::ObjectExt,
}; };
use lemmy_api_structs::blocking;
use lemmy_db_queries::{ApubObject, DbPool, Followable}; use lemmy_db_queries::{ApubObject, DbPool, Followable};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
community::{Community, CommunityFollower, CommunityFollowerForm}, community::{Community, CommunityFollower, CommunityFollowerForm},
person::Person, user::User_,
}; };
use lemmy_structs::blocking;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use url::Url; use url::Url;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ActorType for Person { impl ActorType for User_ {
fn is_local(&self) -> bool {
self.local
}
fn actor_id(&self) -> Url { fn actor_id(&self) -> Url {
self.actor_id.to_owned().into_inner() self.actor_id.to_owned().into_inner()
} }
@ -40,15 +37,7 @@ impl ActorType for Person {
self.private_key.to_owned() self.private_key.to_owned()
} }
fn get_shared_inbox_or_inbox_url(&self) -> Url { /// As a given local user, send out a follow request to a remote community.
self
.shared_inbox_url
.clone()
.unwrap_or_else(|| self.inbox_url.to_owned())
.into()
}
/// As a given local person, send out a follow request to a remote community.
async fn send_follow( async fn send_follow(
&self, &self,
follow_actor_id: &Url, follow_actor_id: &Url,
@ -62,7 +51,7 @@ impl ActorType for Person {
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: community.id, community_id: community.id,
person_id: self.id, user_id: self.id,
pending: true, pending: true,
}; };
blocking(&context.pool(), move |conn| { blocking(&context.pool(), move |conn| {
@ -76,7 +65,7 @@ impl ActorType for Person {
.set_id(generate_activity_id(FollowType::Follow)?) .set_id(generate_activity_id(FollowType::Follow)?)
.set_to(community.actor_id()); .set_to(community.actor_id());
send_activity_single_dest(follow, self, community.inbox_url.into(), context).await?; send_activity_single_dest(follow, self, community.get_inbox_url()?, context).await?;
Ok(()) Ok(())
} }
@ -107,7 +96,7 @@ impl ActorType for Person {
.set_id(generate_activity_id(UndoType::Undo)?) .set_id(generate_activity_id(UndoType::Undo)?)
.set_to(community.actor_id()); .set_to(community.actor_id());
send_activity_single_dest(undo, self, community.inbox_url.into(), context).await?; send_activity_single_dest(undo, self, community.get_inbox_url()?, context).await?;
Ok(()) Ok(())
} }

View file

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

View file

@ -6,11 +6,12 @@ pub(crate) fn lemmy_context() -> Result<Vec<AnyBase>, LemmyError> {
let context_ext = AnyBase::from_arbitrary_json(json!( let context_ext = AnyBase::from_arbitrary_json(json!(
{ {
"sc": "http://schema.org#", "sc": "http://schema.org#",
"category": "sc:category",
"sensitive": "as:sensitive", "sensitive": "as:sensitive",
"stickied": "as:stickied", "stickied": "as:stickied",
"comments_enabled": { "comments_enabled": {
"kind": "sc:Boolean", "kind": "sc:Boolean",
"id": "pt:commentsEnabled" "id": "pt:commentsEnabled"
} }
}))?; }))?;
Ok(vec![AnyBase::from(context()), context_ext]) Ok(vec![AnyBase::from(context()), context_ext])

View file

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

View file

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

View file

@ -95,7 +95,7 @@ pub(crate) fn verify_signature(
} }
} }
/// Extension for actor public key, which is needed on person and community for HTTP signatures. /// Extension for actor public key, which is needed on user and community for HTTP signatures.
/// ///
/// Taken from: https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html /// Taken from: https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]

View file

@ -1,24 +1,29 @@
use crate::{ use crate::{
check_is_apub_id_valid,
fetcher::{ fetcher::{
fetch::fetch_remote_object, fetch::fetch_remote_object,
get_or_fetch_and_upsert_person, get_or_fetch_and_upsert_user,
is_deleted, is_deleted,
should_refetch_actor, should_refetch_actor,
}, },
inbox::person_inbox::receive_announce,
objects::FromApub, objects::FromApub,
ActorType,
GroupExt, GroupExt,
PageExt,
}; };
use activitystreams::{ use activitystreams::{
actor::ApActorExt, base::{BaseExt, ExtendsExt},
collection::{CollectionExt, OrderedCollection}, collection::{CollectionExt, OrderedCollection},
object::ObjectExt, object::ObjectExt,
}; };
use anyhow::Context; use anyhow::Context;
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking;
use lemmy_db_queries::{source::community::Community_, ApubObject, Joinable}; use lemmy_db_queries::{source::community::Community_, ApubObject, Joinable};
use lemmy_db_schema::source::community::{Community, CommunityModerator, CommunityModeratorForm}; use lemmy_db_schema::source::{
community::{Community, CommunityModerator, CommunityModeratorForm},
post::Post,
};
use lemmy_structs::blocking;
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use log::debug; use log::debug;
@ -92,7 +97,7 @@ async fn fetch_remote_community(
let mut creator_and_moderators = Vec::new(); let mut creator_and_moderators = Vec::new();
for uri in creator_and_moderator_uris { for uri in creator_and_moderator_uris {
let c_or_m = get_or_fetch_and_upsert_person(uri, context, recursion_counter).await?; let c_or_m = get_or_fetch_and_upsert_user(uri, context, recursion_counter).await?;
creator_and_moderators.push(c_or_m); creator_and_moderators.push(c_or_m);
} }
@ -104,7 +109,7 @@ async fn fetch_remote_community(
for mod_ in creator_and_moderators { for mod_ in creator_and_moderators {
let community_moderator_form = CommunityModeratorForm { let community_moderator_form = CommunityModeratorForm {
community_id, community_id,
person_id: mod_.id, user_id: mod_.id,
}; };
CommunityModerator::join(conn, &community_moderator_form)?; CommunityModerator::join(conn, &community_moderator_form)?;
@ -114,32 +119,29 @@ async fn fetch_remote_community(
.await??; .await??;
} }
// only fetch outbox for new communities, otherwise this can create an infinite loop // fetch outbox (maybe make this conditional)
if old_community.is_none() { let outbox = fetch_remote_object::<OrderedCollection>(
let outbox = group.inner.outbox()?.context(location_info!())?; context.client(),
fetch_community_outbox(context, outbox, &community, recursion_counter).await? &community.get_outbox_url()?,
recursion_counter,
)
.await?;
let outbox_items = outbox.items().context(location_info!())?.clone();
let mut outbox_items = outbox_items.many().context(location_info!())?;
if outbox_items.len() > 20 {
outbox_items = outbox_items[0..20].to_vec();
}
for o in outbox_items {
let page = PageExt::from_any_base(o)?.context(location_info!())?;
let page_id = page.id_unchecked().context(location_info!())?;
// The post creator may be from a blocked instance, if it errors, then skip it
if check_is_apub_id_valid(page_id).is_err() {
continue;
}
Post::from_apub(&page, context, page_id.to_owned(), recursion_counter).await?;
// TODO: we need to send a websocket update here
} }
Ok(community) Ok(community)
} }
async fn fetch_community_outbox(
context: &LemmyContext,
outbox: &Url,
community: &Community,
recursion_counter: &mut i32,
) -> Result<(), LemmyError> {
let outbox =
fetch_remote_object::<OrderedCollection>(context.client(), outbox, recursion_counter).await?;
let outbox_activities = outbox.items().context(location_info!())?.clone();
let mut outbox_activities = outbox_activities.many().context(location_info!())?;
if outbox_activities.len() > 20 {
outbox_activities = outbox_activities[0..20].to_vec();
}
for activity in outbox_activities {
receive_announce(context, activity, community, recursion_counter).await?;
}
Ok(())
}

View file

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

View file

@ -1,9 +1,9 @@
use crate::{fetcher::fetch::fetch_remote_object, objects::FromApub, NoteExt, PageExt}; use crate::{fetcher::fetch::fetch_remote_object, objects::FromApub, NoteExt, PageExt};
use anyhow::anyhow; use anyhow::anyhow;
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking;
use lemmy_db_queries::{ApubObject, Crud}; use lemmy_db_queries::{ApubObject, Crud};
use lemmy_db_schema::source::{comment::Comment, post::Post}; use lemmy_db_schema::source::{comment::Comment, post::Post};
use lemmy_structs::blocking;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use log::debug; use log::debug;

View file

@ -2,7 +2,7 @@ use crate::{
fetcher::{ fetcher::{
fetch::fetch_remote_object, fetch::fetch_remote_object,
get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_community,
get_or_fetch_and_upsert_person, get_or_fetch_and_upsert_user,
is_deleted, is_deleted,
}, },
find_object_by_id, find_object_by_id,
@ -15,34 +15,34 @@ use crate::{
}; };
use activitystreams::base::BaseExt; use activitystreams::base::BaseExt;
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use lemmy_api_structs::{blocking, site::SearchResponse};
use lemmy_db_queries::{ use lemmy_db_queries::{
source::{ source::{
comment::Comment_, comment::Comment_,
community::Community_, community::Community_,
person::Person_,
post::Post_, post::Post_,
private_message::PrivateMessage_, private_message::PrivateMessage_,
user::User,
}, },
SearchType, SearchType,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
comment::Comment, comment::Comment,
community::Community, community::Community,
person::Person,
post::Post, post::Post,
private_message::PrivateMessage, private_message::PrivateMessage,
user::User_,
}; };
use lemmy_db_views::{comment_view::CommentView, post_view::PostView}; use lemmy_db_views::{comment_view::CommentView, post_view::PostView};
use lemmy_db_views_actor::{community_view::CommunityView, person_view::PersonViewSafe}; use lemmy_db_views_actor::{community_view::CommunityView, user_view::UserViewSafe};
use lemmy_utils::{settings::structs::Settings, LemmyError}; use lemmy_structs::{blocking, site::SearchResponse};
use lemmy_utils::{settings::Settings, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use log::debug; use log::debug;
use url::Url; use url::Url;
/// The types of ActivityPub objects that can be fetched directly by searching for their ID. /// The types of ActivityPub objects that can be fetched directly by searching for their ID.
#[derive(serde::Deserialize, Debug)]
#[serde(untagged)] #[serde(untagged)]
#[derive(serde::Deserialize, Debug)]
enum SearchAcceptedObjects { enum SearchAcceptedObjects {
Person(Box<PersonExt>), Person(Box<PersonExt>),
Group(Box<GroupExt>), Group(Box<GroupExt>),
@ -66,7 +66,7 @@ pub async fn search_by_apub_id(
debug!("Search for {}", query); debug!("Search for {}", query);
let split = query.split('@').collect::<Vec<&str>>(); let split = query.split('@').collect::<Vec<&str>>();
// Person type will look like ['', username, instance] // User type will look like ['', username, instance]
// Community will look like [!community, instance] // Community will look like [!community, instance]
let (name, instance) = if split.len() == 3 { let (name, instance) = if split.len() == 3 {
(format!("/u/{}", split[1]), split[2]) (format!("/u/{}", split[1]), split[2])
@ -122,13 +122,13 @@ async fn build_response(
match fetch_response { match fetch_response {
SearchAcceptedObjects::Person(p) => { SearchAcceptedObjects::Person(p) => {
let person_uri = p.inner.id(domain)?.context("person has no id")?; let user_uri = p.inner.id(domain)?.context("person has no id")?;
let person = get_or_fetch_and_upsert_person(&person_uri, context, recursion_counter).await?; let user = get_or_fetch_and_upsert_user(&user_uri, context, recursion_counter).await?;
response.users = vec![ response.users = vec![
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
PersonViewSafe::read(conn, person.id) UserViewSafe::read(conn, user.id)
}) })
.await??, .await??,
]; ];
@ -182,10 +182,10 @@ async fn delete_object_locally(query_url: &Url, context: &LemmyContext) -> Resul
}) })
.await??; .await??;
} }
Object::Person(u) => { Object::User(u) => {
// TODO: implement update_deleted() for user, move it to ApubObject trait // TODO: implement update_deleted() for user, move it to ApubObject trait
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
Person::delete_account(conn, u.id) User_::delete_account(conn, u.id)
}) })
.await??; .await??;
} }

View file

@ -5,68 +5,66 @@ use crate::{
}; };
use anyhow::anyhow; use anyhow::anyhow;
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking; use lemmy_db_queries::{source::user::User, ApubObject};
use lemmy_db_queries::{source::person::Person_, ApubObject}; use lemmy_db_schema::source::user::User_;
use lemmy_db_schema::source::person::Person; use lemmy_structs::blocking;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use log::debug; use log::debug;
use url::Url; use url::Url;
/// Get a person from its apub ID. /// Get a user from its apub ID.
/// ///
/// If it exists locally and `!should_refetch_actor()`, it is returned directly from the database. /// If it exists locally and `!should_refetch_actor()`, it is returned directly from the database.
/// Otherwise it is fetched from the remote instance, stored and returned. /// Otherwise it is fetched from the remote instance, stored and returned.
pub(crate) async fn get_or_fetch_and_upsert_person( pub(crate) async fn get_or_fetch_and_upsert_user(
apub_id: &Url, apub_id: &Url,
context: &LemmyContext, context: &LemmyContext,
recursion_counter: &mut i32, recursion_counter: &mut i32,
) -> Result<Person, LemmyError> { ) -> Result<User_, LemmyError> {
let apub_id_owned = apub_id.to_owned(); let apub_id_owned = apub_id.to_owned();
let person = blocking(context.pool(), move |conn| { let user = blocking(context.pool(), move |conn| {
Person::read_from_apub_id(conn, &apub_id_owned.into()) User_::read_from_apub_id(conn, &apub_id_owned.into())
}) })
.await?; .await?;
match person { match user {
// If its older than a day, re-fetch it // If its older than a day, re-fetch it
Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => { Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => {
debug!("Fetching and updating from remote person: {}", apub_id); debug!("Fetching and updating from remote user: {}", apub_id);
let person = let person =
fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await; fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await;
if is_deleted(&person) { if is_deleted(&person) {
// TODO: use Person::update_deleted() once implemented // TODO: use User_::update_deleted() once implemented
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
Person::delete_account(conn, u.id) User_::delete_account(conn, u.id)
}) })
.await??; .await??;
return Err(anyhow!("Person was deleted by remote instance").into()); return Err(anyhow!("User was deleted by remote instance").into());
} else if person.is_err() { } else if person.is_err() {
return Ok(u); return Ok(u);
} }
let person = let user = User_::from_apub(&person?, context, apub_id.to_owned(), recursion_counter).await?;
Person::from_apub(&person?, context, apub_id.to_owned(), recursion_counter).await?;
let person_id = person.id; let user_id = user.id;
blocking(context.pool(), move |conn| { blocking(context.pool(), move |conn| {
Person::mark_as_updated(conn, person_id) User_::mark_as_updated(conn, user_id)
}) })
.await??; .await??;
Ok(person) Ok(user)
} }
Ok(u) => Ok(u), Ok(u) => Ok(u),
Err(NotFound {}) => { Err(NotFound {}) => {
debug!("Fetching and creating remote person: {}", apub_id); debug!("Fetching and creating remote user: {}", apub_id);
let person = let person =
fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await?; fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await?;
let person = let user = User_::from_apub(&person, context, apub_id.to_owned(), recursion_counter).await?;
Person::from_apub(&person, context, apub_id.to_owned(), recursion_counter).await?;
Ok(person) Ok(user)
} }
Err(e) => Err(e.into()), Err(e) => Err(e.into()),
} }

View file

@ -4,9 +4,9 @@ use crate::{
}; };
use actix_web::{body::Body, web, web::Path, HttpResponse}; use actix_web::{body::Body, web, web::Path, HttpResponse};
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking;
use lemmy_db_queries::Crud; use lemmy_db_queries::Crud;
use lemmy_db_schema::{source::comment::Comment, CommentId}; use lemmy_db_schema::source::comment::Comment;
use lemmy_structs::blocking;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::Deserialize; use serde::Deserialize;
@ -21,7 +21,7 @@ pub async fn get_apub_comment(
info: Path<CommentQuery>, info: Path<CommentQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> { ) -> Result<HttpResponse<Body>, LemmyError> {
let id = CommentId(info.comment_id.parse::<i32>()?); let id = info.comment_id.parse::<i32>()?;
let comment = blocking(context.pool(), move |conn| Comment::read(conn, id)).await??; let comment = blocking(context.pool(), move |conn| Comment::read(conn, id)).await??;
if !comment.local { if !comment.local {
return Err(NotFound.into()); return Err(NotFound.into());

View file

@ -5,14 +5,14 @@ use crate::{
ActorType, ActorType,
}; };
use activitystreams::{ use activitystreams::{
base::{AnyBase, BaseExt}, base::{AnyBase, BaseExt, ExtendsExt},
collection::{CollectionExt, OrderedCollection, UnorderedCollection}, collection::{CollectionExt, OrderedCollection, UnorderedCollection},
}; };
use actix_web::{body::Body, web, HttpResponse}; use actix_web::{body::Body, web, HttpResponse};
use lemmy_api_structs::blocking; use lemmy_db_queries::source::{community::Community_, post::Post_};
use lemmy_db_queries::source::{activity::Activity_, community::Community_}; use lemmy_db_schema::source::{community::Community, post::Post};
use lemmy_db_schema::source::{activity::Activity, community::Community};
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView; use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
use lemmy_structs::blocking;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::Deserialize; use serde::Deserialize;
@ -60,7 +60,7 @@ pub async fn get_apub_community_followers(
let mut collection = UnorderedCollection::new(); let mut collection = UnorderedCollection::new();
collection collection
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(community.followers_url.into()) .set_id(community.get_followers_url()?)
.set_total_items(community_followers.len() as u64); .set_total_items(community_followers.len() as u64);
Ok(create_apub_response(&collection)) Ok(create_apub_response(&collection))
} }
@ -76,20 +76,21 @@ pub async fn get_apub_community_outbox(
}) })
.await??; .await??;
let community_actor_id = community.actor_id.to_owned(); let community_id = community.id;
let activities = blocking(context.pool(), move |conn| { let posts = blocking(context.pool(), move |conn| {
Activity::read_community_outbox(conn, &community_actor_id) Post::list_for_community(conn, community_id)
}) })
.await??; .await??;
let activities = activities let mut pages: Vec<AnyBase> = vec![];
.iter() for p in posts {
.map(AnyBase::from_arbitrary_json) pages.push(p.to_apub(context.pool()).await?.into_any_base()?);
.collect::<Result<Vec<AnyBase>, serde_json::Error>>()?; }
let len = activities.len();
let len = pages.len();
let mut collection = OrderedCollection::new(); let mut collection = OrderedCollection::new();
collection collection
.set_many_items(activities) .set_many_items(pages)
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(community.get_outbox_url()?) .set_id(community.get_outbox_url()?)
.set_total_items(len as u64); .set_total_items(len as u64);

View file

@ -1,18 +1,17 @@
use crate::APUB_JSON_CONTENT_TYPE; use crate::APUB_JSON_CONTENT_TYPE;
use actix_web::{body::Body, web, HttpResponse}; use actix_web::{body::Body, web, HttpResponse};
use http::StatusCode; use http::StatusCode;
use lemmy_api_structs::blocking;
use lemmy_db_queries::source::activity::Activity_; use lemmy_db_queries::source::activity::Activity_;
use lemmy_db_schema::source::activity::Activity; use lemmy_db_schema::source::activity::Activity;
use lemmy_utils::{settings::structs::Settings, LemmyError}; use lemmy_structs::blocking;
use lemmy_utils::{settings::Settings, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url;
pub mod comment; pub mod comment;
pub mod community; pub mod community;
pub mod person;
pub mod post; pub mod post;
pub mod user;
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub /// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
/// headers. /// headers.
@ -47,13 +46,12 @@ pub async fn get_activity(
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> { ) -> Result<HttpResponse<Body>, LemmyError> {
let settings = Settings::get(); let settings = Settings::get();
let activity_id = Url::parse(&format!( let activity_id = format!(
"{}/activities/{}/{}", "{}/activities/{}/{}",
settings.get_protocol_and_hostname(), settings.get_protocol_and_hostname(),
info.type_, info.type_,
info.id info.id
))? );
.into();
let activity = blocking(context.pool(), move |conn| { let activity = blocking(context.pool(), move |conn| {
Activity::read_from_apub_id(&conn, &activity_id) Activity::read_from_apub_id(&conn, &activity_id)
}) })

View file

@ -4,9 +4,9 @@ use crate::{
}; };
use actix_web::{body::Body, web, HttpResponse}; use actix_web::{body::Body, web, HttpResponse};
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking;
use lemmy_db_queries::Crud; use lemmy_db_queries::Crud;
use lemmy_db_schema::{source::post::Post, PostId}; use lemmy_db_schema::source::post::Post;
use lemmy_structs::blocking;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::Deserialize; use serde::Deserialize;
@ -21,7 +21,7 @@ pub async fn get_apub_post(
info: web::Path<PostQuery>, info: web::Path<PostQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> { ) -> Result<HttpResponse<Body>, LemmyError> {
let id = PostId(info.post_id.parse::<i32>()?); let id = info.post_id.parse::<i32>()?;
let post = blocking(context.pool(), move |conn| Post::read(conn, id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, id)).await??;
if !post.local { if !post.local {
return Err(NotFound.into()); return Err(NotFound.into());

View file

@ -9,70 +9,70 @@ use activitystreams::{
collection::{CollectionExt, OrderedCollection}, collection::{CollectionExt, OrderedCollection},
}; };
use actix_web::{body::Body, web, HttpResponse}; use actix_web::{body::Body, web, HttpResponse};
use lemmy_api_structs::blocking; use lemmy_db_queries::source::user::User;
use lemmy_db_queries::source::person::Person_; use lemmy_db_schema::source::user::User_;
use lemmy_db_schema::source::person::Person; use lemmy_structs::blocking;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::Deserialize; use serde::Deserialize;
use url::Url; use url::Url;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct PersonQuery { pub struct UserQuery {
user_name: String, user_name: String,
} }
/// Return the ActivityPub json representation of a local person over HTTP. /// Return the ActivityPub json representation of a local user over HTTP.
pub async fn get_apub_person_http( pub async fn get_apub_user_http(
info: web::Path<PersonQuery>, info: web::Path<UserQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> { ) -> Result<HttpResponse<Body>, LemmyError> {
let user_name = info.into_inner().user_name; let user_name = info.into_inner().user_name;
// TODO: this needs to be able to read deleted persons, so that it can send tombstones // TODO: this needs to be able to read deleted users, so that it can send tombstones
let person = blocking(context.pool(), move |conn| { let user = blocking(context.pool(), move |conn| {
Person::find_by_name(conn, &user_name) User_::find_by_email_or_username(conn, &user_name)
}) })
.await??; .await??;
if !person.deleted { if !user.deleted {
let apub = person.to_apub(context.pool()).await?; let apub = user.to_apub(context.pool()).await?;
Ok(create_apub_response(&apub)) Ok(create_apub_response(&apub))
} else { } else {
Ok(create_apub_tombstone_response(&person.to_tombstone()?)) Ok(create_apub_tombstone_response(&user.to_tombstone()?))
} }
} }
pub async fn get_apub_person_outbox( pub async fn get_apub_user_outbox(
info: web::Path<PersonQuery>, info: web::Path<UserQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> { ) -> Result<HttpResponse<Body>, LemmyError> {
let person = blocking(context.pool(), move |conn| { let user = blocking(context.pool(), move |conn| {
Person::find_by_name(&conn, &info.user_name) User_::read_from_name(&conn, &info.user_name)
}) })
.await??; .await??;
// TODO: populate the person outbox // TODO: populate the user outbox
let mut collection = OrderedCollection::new(); let mut collection = OrderedCollection::new();
collection collection
.set_many_items(Vec::<Url>::new()) .set_many_items(Vec::<Url>::new())
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(person.get_outbox_url()?) .set_id(user.get_outbox_url()?)
.set_total_items(0_u64); .set_total_items(0_u64);
Ok(create_apub_response(&collection)) Ok(create_apub_response(&collection))
} }
pub async fn get_apub_person_inbox( pub async fn get_apub_user_inbox(
info: web::Path<PersonQuery>, info: web::Path<UserQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> { ) -> Result<HttpResponse<Body>, LemmyError> {
let person = blocking(context.pool(), move |conn| { let user = blocking(context.pool(), move |conn| {
Person::find_by_name(&conn, &info.user_name) User_::read_from_name(&conn, &info.user_name)
}) })
.await??; .await??;
let mut collection = OrderedCollection::new(); let mut collection = OrderedCollection::new();
collection collection
.set_id(format!("{}/inbox", person.actor_id.into_inner()).parse()?) .set_id(format!("{}/inbox", user.actor_id.into_inner()).parse()?)
.set_many_contexts(lemmy_context()?); .set_many_contexts(lemmy_context()?);
Ok(create_apub_response(&collection)) Ok(create_apub_response(&collection))
} }

View file

@ -26,16 +26,13 @@ use activitystreams::{
}; };
use actix_web::{web, HttpRequest, HttpResponse}; use actix_web::{web, HttpRequest, HttpResponse};
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use lemmy_api_structs::blocking;
use lemmy_db_queries::{source::community::Community_, ApubObject, DbPool, Followable}; use lemmy_db_queries::{source::community::Community_, ApubObject, DbPool, Followable};
use lemmy_db_schema::{ use lemmy_db_schema::source::{
source::{ community::{Community, CommunityFollower, CommunityFollowerForm},
community::{Community, CommunityFollower, CommunityFollowerForm}, user::User_,
person::Person,
},
CommunityId,
}; };
use lemmy_db_views_actor::community_person_ban_view::CommunityPersonBanView; use lemmy_db_views_actor::community_user_ban_view::CommunityUserBanView;
use lemmy_structs::blocking;
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use log::info; use log::info;
@ -47,8 +44,8 @@ use url::Url;
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub enum CommunityValidTypes { pub enum CommunityValidTypes {
Follow, // follow request from a person Follow, // follow request from a user
Undo, // unfollow from a person Undo, // unfollow from a user
Create, // create post or comment Create, // create post or comment
Update, // update post or comment Update, // update post or comment
Like, // upvote post or comment Like, // upvote post or comment
@ -116,21 +113,21 @@ pub(crate) async fn community_receive_message(
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
// Only persons can send activities to the community, so we can get the actor as person // Only users can send activities to the community, so we can get the actor as user
// unconditionally. // unconditionally.
let actor_id = actor.actor_id(); let actor_id = actor.actor_id();
let person = blocking(&context.pool(), move |conn| { let user = blocking(&context.pool(), move |conn| {
Person::read_from_apub_id(&conn, &actor_id.into()) User_::read_from_apub_id(&conn, &actor_id.into())
}) })
.await??; .await??;
check_community_or_site_ban(&person, to_community.id, context.pool()).await?; check_community_or_site_ban(&user, &to_community, context.pool()).await?;
let any_base = activity.clone().into_any_base()?; 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 activity_kind = activity.kind().context(location_info!())?;
let do_announce = match activity_kind { let do_announce = match activity_kind {
CommunityValidTypes::Follow => { CommunityValidTypes::Follow => {
handle_follow(any_base.clone(), person, &to_community, &context).await?; handle_follow(any_base.clone(), user, &to_community, &context).await?;
false false
} }
CommunityValidTypes::Undo => { CommunityValidTypes::Undo => {
@ -165,7 +162,7 @@ pub(crate) async fn community_receive_message(
} }
CommunityValidTypes::Remove => { CommunityValidTypes::Remove => {
// TODO: we dont support remote mods, so this is ignored for now // TODO: we dont support remote mods, so this is ignored for now
//receive_remove_for_community(context, any_base.clone(), &person_url).await? //receive_remove_for_community(context, any_base.clone(), &user_url).await?
false false
} }
}; };
@ -181,20 +178,20 @@ pub(crate) async fn community_receive_message(
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
/// Handle a follow request from a remote person, adding the person as follower and returning an /// Handle a follow request from a remote user, adding the user as follower and returning an
/// Accept activity. /// Accept activity.
async fn handle_follow( async fn handle_follow(
activity: AnyBase, activity: AnyBase,
person: Person, user: User_,
community: &Community, community: &Community,
context: &LemmyContext, context: &LemmyContext,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let follow = Follow::from_any_base(activity)?.context(location_info!())?; let follow = Follow::from_any_base(activity)?.context(location_info!())?;
verify_activity_domains_valid(&follow, &person.actor_id(), false)?; verify_activity_domains_valid(&follow, &user.actor_id(), false)?;
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: community.id, community_id: community.id,
person_id: person.id, user_id: user.id,
pending: false, pending: false,
}; };
@ -229,27 +226,27 @@ async fn handle_undo(
} }
} }
/// Handle `Undo/Follow` from a person, removing the person from followers list. /// Handle `Undo/Follow` from a user, removing the user from followers list.
async fn handle_undo_follow( async fn handle_undo_follow(
activity: AnyBase, activity: AnyBase,
person_url: Url, user_url: Url,
community: &Community, community: &Community,
context: &LemmyContext, context: &LemmyContext,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let undo = Undo::from_any_base(activity)?.context(location_info!())?; let undo = Undo::from_any_base(activity)?.context(location_info!())?;
verify_activity_domains_valid(&undo, &person_url, true)?; verify_activity_domains_valid(&undo, &user_url, true)?;
let object = undo.object().to_owned().one().context(location_info!())?; let object = undo.object().to_owned().one().context(location_info!())?;
let follow = Follow::from_any_base(object)?.context(location_info!())?; let follow = Follow::from_any_base(object)?.context(location_info!())?;
verify_activity_domains_valid(&follow, &person_url, false)?; verify_activity_domains_valid(&follow, &user_url, false)?;
let person = blocking(&context.pool(), move |conn| { let user = blocking(&context.pool(), move |conn| {
Person::read_from_apub_id(&conn, &person_url.into()) User_::read_from_apub_id(&conn, &user_url.into())
}) })
.await??; .await??;
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: community.id, community_id: community.id,
person_id: person.id, user_id: user.id,
pending: false, pending: false,
}; };
@ -263,18 +260,18 @@ async fn handle_undo_follow(
} }
pub(crate) async fn check_community_or_site_ban( pub(crate) async fn check_community_or_site_ban(
person: &Person, user: &User_,
community_id: CommunityId, community: &Community,
pool: &DbPool, pool: &DbPool,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
if person.banned { if user.banned {
return Err(anyhow!("Person is banned from site").into()); return Err(anyhow!("User is banned from site").into());
} }
let person_id = person.id; let user_id = user.id;
let is_banned = let community_id = community.id;
move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok(); let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
if blocking(pool, is_banned).await? { if blocking(pool, is_banned).await? {
return Err(anyhow!("Person is banned from community").into()); return Err(anyhow!("User is banned from community").into());
} }
Ok(()) Ok(())

View file

@ -12,23 +12,18 @@ use activitystreams::{
}; };
use actix_web::HttpRequest; use actix_web::HttpRequest;
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use lemmy_api_structs::blocking; use lemmy_db_queries::{source::activity::Activity_, ApubObject, DbPool};
use lemmy_db_queries::{ use lemmy_db_schema::source::{activity::Activity, community::Community, user::User_};
source::{activity::Activity_, community::Community_}, use lemmy_structs::blocking;
ApubObject, use lemmy_utils::{location_info, settings::Settings, LemmyError};
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 lemmy_websocket::LemmyContext;
use serde::Serialize; use serde::{export::fmt::Debug, Serialize};
use std::fmt::Debug;
use url::Url; use url::Url;
pub mod community_inbox; pub mod community_inbox;
pub mod person_inbox;
mod receive_for_community; mod receive_for_community;
pub mod shared_inbox; pub mod shared_inbox;
pub mod user_inbox;
pub(crate) fn get_activity_id<T, Kind>(activity: &T, creator_uri: &Url) -> Result<Url, LemmyError> pub(crate) fn get_activity_id<T, Kind>(activity: &T, creator_uri: &Url) -> Result<Url, LemmyError>
where where
@ -45,7 +40,7 @@ pub(crate) async fn is_activity_already_known(
pool: &DbPool, pool: &DbPool,
activity_id: &Url, activity_id: &Url,
) -> Result<bool, LemmyError> { ) -> Result<bool, LemmyError> {
let activity_id = activity_id.to_owned().into(); let activity_id = activity_id.to_string();
let existing = blocking(pool, move |conn| { let existing = blocking(pool, move |conn| {
Activity::read_from_apub_id(&conn, &activity_id) Activity::read_from_apub_id(&conn, &activity_id)
}) })
@ -119,17 +114,17 @@ where
} }
/// Returns true if `to_and_cc` contains at least one local user. /// Returns true if `to_and_cc` contains at least one local user.
pub(crate) async fn is_addressed_to_local_person( pub(crate) async fn is_addressed_to_local_user(
to_and_cc: &[Url], to_and_cc: &[Url],
pool: &DbPool, pool: &DbPool,
) -> Result<bool, LemmyError> { ) -> Result<bool, LemmyError> {
for url in to_and_cc { for url in to_and_cc {
let url = url.to_owned(); let url = url.to_owned();
let person = blocking(&pool, move |conn| { let user = blocking(&pool, move |conn| {
Person::read_from_apub_id(&conn, &url.into()) User_::read_from_apub_id(&conn, &url.into())
}) })
.await?; .await?;
if let Ok(u) = person { if let Ok(u) = user {
if u.local { if u.local {
return Ok(true); return Ok(true);
} }
@ -145,15 +140,16 @@ pub(crate) async fn is_addressed_to_community_followers(
pool: &DbPool, pool: &DbPool,
) -> Result<Option<Community>, LemmyError> { ) -> Result<Option<Community>, LemmyError> {
for url in to_and_cc { for url in to_and_cc {
let url = url.to_owned().into(); let url = url.to_string();
let community = blocking(&pool, move |conn| { // TODO: extremely hacky, we should just store the followers url for each community in the db
// ignore errors here, because the current url might not actually be a followers url if url.ends_with("/followers") {
Community::read_from_followers_url(&conn, &url).ok() let community_url = Url::parse(&url.replace("/followers", ""))?;
}) let community = blocking(&pool, move |conn| {
.await?; Community::read_from_apub_id(&conn, &community_url.into())
if let Some(c) = community { })
if !c.local { .await??;
return Ok(Some(c)); if !community.local {
return Ok(Some(community));
} }
} }
} }
@ -167,7 +163,7 @@ where
let id = activity.id_unchecked().context(location_info!())?; let id = activity.id_unchecked().context(location_info!())?;
let activity_domain = id.domain().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( return Err(
anyhow!( anyhow!(
"Error: received activity which was sent by local instance: {:?}", "Error: received activity which was sent by local instance: {:?}",

View file

@ -43,22 +43,15 @@ use activitystreams::{
}; };
use anyhow::Context; use anyhow::Context;
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking;
use lemmy_db_queries::Crud; use lemmy_db_queries::Crud;
use lemmy_db_schema::source::site::Site; use lemmy_db_schema::source::site::Site;
use lemmy_structs::blocking;
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use strum_macros::EnumString;
use url::Url; use url::Url;
#[derive(EnumString)]
enum PageOrNote {
Page,
Note,
}
/// This file is for post/comment activities received by the community, and for post/comment /// This file is for post/comment activities received by the community, and for post/comment
/// activities announced by the community and received by the person. /// activities announced by the community and received by the user.
/// A post or comment being created /// A post or comment being created
pub(in crate::inbox) async fn receive_create_for_community( pub(in crate::inbox) async fn receive_create_for_community(
@ -71,13 +64,9 @@ pub(in crate::inbox) async fn receive_create_for_community(
verify_activity_domains_valid(&create, &expected_domain, true)?; verify_activity_domains_valid(&create, &expected_domain, true)?;
is_addressed_to_public(&create)?; is_addressed_to_public(&create)?;
let kind = create match create.object().as_single_kind_str() {
.object() Some("Page") => receive_create_post(create, context, request_counter).await,
.as_single_kind_str() Some("Note") => receive_create_comment(create, context, request_counter).await,
.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), _ => receive_unhandled_activity(create),
} }
} }
@ -93,13 +82,9 @@ pub(in crate::inbox) async fn receive_update_for_community(
verify_activity_domains_valid(&update, &expected_domain, true)?; verify_activity_domains_valid(&update, &expected_domain, true)?;
is_addressed_to_public(&update)?; is_addressed_to_public(&update)?;
let kind = update match update.object().as_single_kind_str() {
.object() Some("Page") => receive_update_post(update, context, request_counter).await,
.as_single_kind_str() Some("Note") => receive_update_comment(update, context, request_counter).await,
.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), _ => receive_unhandled_activity(update),
} }
} }
@ -115,14 +100,11 @@ pub(in crate::inbox) async fn receive_like_for_community(
verify_activity_domains_valid(&like, &expected_domain, false)?; verify_activity_domains_valid(&like, &expected_domain, false)?;
is_addressed_to_public(&like)?; is_addressed_to_public(&like)?;
let object_id = like let object_id = get_like_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? { 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) => { PostOrComment::Comment(comment) => {
receive_like_comment(like, *comment, context, request_counter).await receive_like_comment(like, comment, context, request_counter).await
} }
} }
} }
@ -146,16 +128,13 @@ pub(in crate::inbox) async fn receive_dislike_for_community(
verify_activity_domains_valid(&dislike, &expected_domain, false)?; verify_activity_domains_valid(&dislike, &expected_domain, false)?;
is_addressed_to_public(&dislike)?; is_addressed_to_public(&dislike)?;
let object_id = dislike let object_id = get_like_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? { match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
PostOrComment::Post(post) => { PostOrComment::Post(post) => {
receive_dislike_post(dislike, *post, context, request_counter).await receive_dislike_post(dislike, post, context, request_counter).await
} }
PostOrComment::Comment(comment) => { PostOrComment::Comment(comment) => {
receive_dislike_comment(dislike, *comment, context, request_counter).await receive_dislike_comment(dislike, comment, context, request_counter).await
} }
} }
} }
@ -177,8 +156,8 @@ pub(in crate::inbox) async fn receive_delete_for_community(
.context(location_info!())?; .context(location_info!())?;
match find_post_or_comment_by_id(context, object).await { match find_post_or_comment_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_delete_post(context, *p).await, Ok(PostOrComment::Post(p)) => receive_delete_post(context, p).await,
Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, *c).await, Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, c).await,
// if we dont have the object, no need to do anything // if we dont have the object, no need to do anything
Err(_) => Ok(()), Err(_) => Ok(()),
} }
@ -215,21 +194,13 @@ pub(in crate::inbox) async fn receive_remove_for_community(
remove.id(community_id.domain().context(location_info!())?)?; remove.id(community_id.domain().context(location_info!())?)?;
match find_post_or_comment_by_id(context, object).await { match find_post_or_comment_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, *p).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::Comment(c)) => receive_remove_comment(context, remove, c).await,
// if we dont have the object, no need to do anything // if we dont have the object, no need to do anything
Err(_) => Ok(()), Err(_) => Ok(()),
} }
} }
#[derive(EnumString)]
enum UndoableActivities {
Delete,
Remove,
Like,
Dislike,
}
/// A post/comment action being reverted (either a delete, remove, upvote or downvote) /// A post/comment action being reverted (either a delete, remove, upvote or downvote)
pub(in crate::inbox) async fn receive_undo_for_community( pub(in crate::inbox) async fn receive_undo_for_community(
context: &LemmyContext, context: &LemmyContext,
@ -241,18 +212,13 @@ pub(in crate::inbox) async fn receive_undo_for_community(
verify_activity_domains_valid(&undo, &expected_domain.to_owned(), true)?; verify_activity_domains_valid(&undo, &expected_domain.to_owned(), true)?;
is_addressed_to_public(&undo)?; is_addressed_to_public(&undo)?;
use UndoableActivities::*; match undo.object().as_single_kind_str() {
match undo Some("Delete") => receive_undo_delete_for_community(context, undo, expected_domain).await,
.object() Some("Remove") => receive_undo_remove_for_community(context, undo, expected_domain).await,
.as_single_kind_str() Some("Like") => {
.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 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_undo_dislike_for_community(context, undo, expected_domain, request_counter).await
} }
_ => receive_unhandled_activity(undo), _ => receive_unhandled_activity(undo),
@ -276,8 +242,8 @@ pub(in crate::inbox) async fn receive_undo_delete_for_community(
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
match find_post_or_comment_by_id(context, object).await { match find_post_or_comment_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_undo_delete_post(context, *p).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::Comment(c)) => receive_undo_delete_comment(context, c).await,
// if we dont have the object, no need to do anything // if we dont have the object, no need to do anything
Err(_) => Ok(()), Err(_) => Ok(()),
} }
@ -300,8 +266,8 @@ pub(in crate::inbox) async fn receive_undo_remove_for_community(
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
match find_post_or_comment_by_id(context, object).await { match find_post_or_comment_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_undo_remove_post(context, *p).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::Comment(c)) => receive_undo_remove_comment(context, c).await,
// if we dont have the object, no need to do anything // if we dont have the object, no need to do anything
Err(_) => Ok(()), Err(_) => Ok(()),
} }
@ -319,16 +285,13 @@ pub(in crate::inbox) async fn receive_undo_like_for_community(
verify_activity_domains_valid(&like, &expected_domain, false)?; verify_activity_domains_valid(&like, &expected_domain, false)?;
is_addressed_to_public(&like)?; is_addressed_to_public(&like)?;
let object_id = like let object_id = get_like_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? { match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
PostOrComment::Post(post) => { 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) => { PostOrComment::Comment(comment) => {
receive_undo_like_comment(&like, *comment, context, request_counter).await receive_undo_like_comment(&like, comment, context, request_counter).await
} }
} }
} }
@ -345,16 +308,13 @@ pub(in crate::inbox) async fn receive_undo_dislike_for_community(
verify_activity_domains_valid(&dislike, &expected_domain, false)?; verify_activity_domains_valid(&dislike, &expected_domain, false)?;
is_addressed_to_public(&dislike)?; is_addressed_to_public(&dislike)?;
let object_id = dislike let object_id = get_like_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? { match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
PostOrComment::Post(post) => { 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) => { PostOrComment::Comment(comment) => {
receive_undo_dislike_comment(&dislike, *comment, context, request_counter).await receive_undo_dislike_comment(&dislike, comment, context, request_counter).await
} }
} }
} }
@ -365,12 +325,35 @@ async fn fetch_post_or_comment_by_id(
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<PostOrComment, LemmyError> { ) -> Result<PostOrComment, LemmyError> {
if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await { if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await {
return Ok(PostOrComment::Post(Box::new(post))); return Ok(PostOrComment::Post(post));
} }
if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await { if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await {
return Ok(PostOrComment::Comment(Box::new(comment))); return Ok(PostOrComment::Comment(comment));
} }
Err(NotFound.into()) 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(),
)
}
}

View file

@ -7,17 +7,17 @@ use crate::{
inbox_verify_http_signature, inbox_verify_http_signature,
is_activity_already_known, is_activity_already_known,
is_addressed_to_community_followers, is_addressed_to_community_followers,
is_addressed_to_local_person, is_addressed_to_local_user,
person_inbox::{person_receive_message, PersonAcceptedActivities}, user_inbox::{user_receive_message, UserAcceptedActivities},
}, },
insert_activity, insert_activity,
}; };
use activitystreams::{activity::ActorAndObject, prelude::*}; use activitystreams::{activity::ActorAndObject, prelude::*};
use actix_web::{web, HttpRequest, HttpResponse}; use actix_web::{web, HttpRequest, HttpResponse};
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::blocking;
use lemmy_db_queries::{ApubObject, DbPool}; use lemmy_db_queries::{ApubObject, DbPool};
use lemmy_db_schema::source::community::Community; use lemmy_db_schema::source::community::Community;
use lemmy_structs::blocking;
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -69,9 +69,9 @@ pub async fn shared_inbox(
let mut res: Option<HttpResponse> = None; let mut res: Option<HttpResponse> = None;
let to_and_cc = get_activity_to_and_cc(&activity); 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. // Handle community first, so in case the sender is banned by the community, it will error out.
// If we handled the person receive first, the activity would be inserted to the database before the // If we handled the user receive first, the activity would be inserted to the database before the
// community could check for bans. // community could check for bans.
// Note that an activity can be addressed to a community and to a person (or multiple persons) at the // Note that an activity can be addressed to a community and to a user (or multiple users) at the
// same time. In this case we still only handle it once, to avoid duplicate websocket // same time. In this case we still only handle it once, to avoid duplicate websocket
// notifications. // notifications.
let community = extract_local_community_from_destinations(&to_and_cc, context.pool()).await?; let community = extract_local_community_from_destinations(&to_and_cc, context.pool()).await?;
@ -88,13 +88,13 @@ pub async fn shared_inbox(
) )
.await?, .await?,
); );
} else if is_addressed_to_local_person(&to_and_cc, context.pool()).await? { } else if is_addressed_to_local_user(&to_and_cc, context.pool()).await? {
let person_activity = PersonAcceptedActivities::from_any_base(activity_any_base.clone())? let user_activity = UserAcceptedActivities::from_any_base(activity_any_base.clone())?
.context(location_info!())?; .context(location_info!())?;
// `to_person` is only used for follow activities (which we dont receive here), so no need to pass // `to_user` is only used for follow activities (which we dont receive here), so no need to pass
// it in // it in
person_receive_message( user_receive_message(
person_activity, user_activity,
None, None,
actor.as_ref(), actor.as_ref(),
&context, &context,
@ -105,11 +105,11 @@ pub async fn shared_inbox(
.await? .await?
.is_some() .is_some()
{ {
let person_activity = PersonAcceptedActivities::from_any_base(activity_any_base.clone())? let user_activity = UserAcceptedActivities::from_any_base(activity_any_base.clone())?
.context(location_info!())?; .context(location_info!())?;
res = Some( res = Some(
person_receive_message( user_receive_message(
person_activity, user_activity,
None, None,
actor.as_ref(), actor.as_ref(),
&context, &context,

View file

@ -25,7 +25,7 @@ use crate::{
inbox_verify_http_signature, inbox_verify_http_signature,
is_activity_already_known, is_activity_already_known,
is_addressed_to_community_followers, is_addressed_to_community_followers,
is_addressed_to_local_person, is_addressed_to_local_user,
is_addressed_to_public, is_addressed_to_public,
receive_for_community::{ receive_for_community::{
receive_create_for_community, receive_create_for_community,
@ -48,25 +48,24 @@ use activitystreams::{
use actix_web::{web, HttpRequest, HttpResponse}; use actix_web::{web, HttpRequest, HttpResponse};
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use diesel::NotFound; use diesel::NotFound;
use lemmy_api_structs::blocking; use lemmy_db_queries::{source::user::User, ApubObject, Followable};
use lemmy_db_queries::{source::person::Person_, ApubObject, Followable};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
community::{Community, CommunityFollower}, community::{Community, CommunityFollower},
person::Person,
private_message::PrivateMessage, private_message::PrivateMessage,
user::User_,
}; };
use lemmy_structs::blocking;
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use log::debug; use log::debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use strum_macros::EnumString;
use url::Url; use url::Url;
/// Allowed activities for person inbox. /// Allowed activities for user inbox.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
pub enum PersonValidTypes { pub enum UserValidTypes {
Accept, // community accepted our follow request Accept, // community accepted our follow request
Create, // create private message Create, // create private message
Update, // edit private message Update, // edit private message
@ -76,12 +75,12 @@ pub enum PersonValidTypes {
Announce, // post, comment or vote in community Announce, // post, comment or vote in community
} }
pub type PersonAcceptedActivities = ActorAndObject<PersonValidTypes>; pub type UserAcceptedActivities = ActorAndObject<UserValidTypes>;
/// Handler for all incoming activities to person inboxes. /// Handler for all incoming activities to user inboxes.
pub async fn person_inbox( pub async fn user_inbox(
request: HttpRequest, request: HttpRequest,
input: web::Json<PersonAcceptedActivities>, input: web::Json<UserAcceptedActivities>,
path: web::Path<String>, path: web::Path<String>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
@ -98,29 +97,29 @@ pub async fn person_inbox(
// Check if the activity is actually meant for us // Check if the activity is actually meant for us
let username = path.into_inner(); let username = path.into_inner();
let person = blocking(&context.pool(), move |conn| { let user = blocking(&context.pool(), move |conn| {
Person::find_by_name(&conn, &username) User_::read_from_name(&conn, &username)
}) })
.await??; .await??;
let to_and_cc = get_activity_to_and_cc(&activity); let to_and_cc = get_activity_to_and_cc(&activity);
// TODO: we should also accept activities that are sent to community followers // TODO: we should also accept activities that are sent to community followers
if !to_and_cc.contains(&&person.actor_id()) { if !to_and_cc.contains(&&user.actor_id()) {
return Err(anyhow!("Activity delivered to wrong person").into()); return Err(anyhow!("Activity delivered to wrong user").into());
} }
assert_activity_not_local(&activity)?; assert_activity_not_local(&activity)?;
insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?; insert_activity(&activity_id, activity.clone(), false, true, context.pool()).await?;
debug!( debug!(
"Person {} received activity {:?} from {}", "User {} received activity {:?} from {}",
person.name, user.name,
&activity.id_unchecked(), &activity.id_unchecked(),
&actor.actor_id() &actor.actor_id()
); );
person_receive_message( user_receive_message(
activity.clone(), activity.clone(),
Some(person.clone()), Some(user.clone()),
actor.as_ref(), actor.as_ref(),
&context, &context,
request_counter, request_counter,
@ -129,43 +128,36 @@ pub async fn person_inbox(
} }
/// Receives Accept/Follow, Announce, private messages and community (undo) remove, (undo) delete /// Receives Accept/Follow, Announce, private messages and community (undo) remove, (undo) delete
pub(crate) async fn person_receive_message( pub(crate) async fn user_receive_message(
activity: PersonAcceptedActivities, activity: UserAcceptedActivities,
to_person: Option<Person>, to_user: Option<User_>,
actor: &dyn ActorType, actor: &dyn ActorType,
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<HttpResponse, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
is_for_person_inbox(context, &activity).await?; is_for_user_inbox(context, &activity).await?;
let any_base = activity.clone().into_any_base()?; let any_base = activity.clone().into_any_base()?;
let kind = activity.kind().context(location_info!())?; let kind = activity.kind().context(location_info!())?;
let actor_url = actor.actor_id(); let actor_url = actor.actor_id();
match kind { match kind {
PersonValidTypes::Accept => { UserValidTypes::Accept => {
receive_accept( receive_accept(&context, any_base, actor, to_user.unwrap(), request_counter).await?;
&context,
any_base,
actor,
to_person.expect("person provided"),
request_counter,
)
.await?;
} }
PersonValidTypes::Announce => { UserValidTypes::Announce => {
receive_announce(&context, any_base, actor, request_counter).await? receive_announce(&context, any_base, actor, request_counter).await?
} }
PersonValidTypes::Create => { UserValidTypes::Create => {
receive_create(&context, any_base, actor_url, request_counter).await? receive_create(&context, any_base, actor_url, request_counter).await?
} }
PersonValidTypes::Update => { UserValidTypes::Update => {
receive_update(&context, any_base, actor_url, request_counter).await? receive_update(&context, any_base, actor_url, request_counter).await?
} }
PersonValidTypes::Delete => { UserValidTypes::Delete => {
receive_delete(context, any_base, &actor_url, request_counter).await? receive_delete(context, any_base, &actor_url, request_counter).await?
} }
PersonValidTypes::Undo => receive_undo(context, any_base, &actor_url, request_counter).await?, UserValidTypes::Undo => receive_undo(context, any_base, &actor_url, request_counter).await?,
PersonValidTypes::Remove => receive_remove_community(&context, any_base, &actor_url).await?, UserValidTypes::Remove => receive_remove_community(&context, any_base, &actor_url).await?,
}; };
// TODO: would be logical to move websocket notification code here // TODO: would be logical to move websocket notification code here
@ -173,16 +165,16 @@ pub(crate) async fn person_receive_message(
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
/// Returns true if the activity is addressed directly to one or more local persons, or if it is /// 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 person follows /// addressed to the followers collection of a remote community, and at least one local user follows
/// it. /// it.
async fn is_for_person_inbox( async fn is_for_user_inbox(
context: &LemmyContext, context: &LemmyContext,
activity: &PersonAcceptedActivities, activity: &UserAcceptedActivities,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let to_and_cc = get_activity_to_and_cc(activity); let to_and_cc = get_activity_to_and_cc(activity);
// Check if it is addressed directly to any local person // Check if it is addressed directly to any local user
if is_addressed_to_local_person(&to_and_cc, context.pool()).await? { if is_addressed_to_local_user(&to_and_cc, context.pool()).await? {
return Ok(()); return Ok(());
} }
@ -205,7 +197,7 @@ async fn is_for_person_inbox(
} }
} }
Err(anyhow!("Not addressed for any local person").into()) Err(anyhow!("Not addressed for any local user").into())
} }
/// Handle accepted follows. /// Handle accepted follows.
@ -213,7 +205,7 @@ async fn receive_accept(
context: &LemmyContext, context: &LemmyContext,
activity: AnyBase, activity: AnyBase,
actor: &dyn ActorType, actor: &dyn ActorType,
person: Person, user: User_,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let accept = Accept::from_any_base(activity)?.context(location_info!())?; let accept = Accept::from_any_base(activity)?.context(location_info!())?;
@ -221,7 +213,7 @@ async fn receive_accept(
let object = accept.object().to_owned().one().context(location_info!())?; let object = accept.object().to_owned().one().context(location_info!())?;
let follow = Follow::from_any_base(object)?.context(location_info!())?; let follow = Follow::from_any_base(object)?.context(location_info!())?;
verify_activity_domains_valid(&follow, &person.actor_id(), false)?; verify_activity_domains_valid(&follow, &user.actor_id(), false)?;
let community_uri = accept let community_uri = accept
.actor()? .actor()?
@ -233,29 +225,18 @@ async fn receive_accept(
get_or_fetch_and_upsert_community(&community_uri, context, request_counter).await?; get_or_fetch_and_upsert_community(&community_uri, context, request_counter).await?;
let community_id = community.id; let community_id = community.id;
let person_id = person.id; let user_id = user.id;
// This will throw an error if no follow was requested // This will throw an error if no follow was requested
blocking(&context.pool(), move |conn| { blocking(&context.pool(), move |conn| {
CommunityFollower::follow_accepted(conn, community_id, person_id) CommunityFollower::follow_accepted(conn, community_id, user_id)
}) })
.await??; .await??;
Ok(()) Ok(())
} }
#[derive(EnumString)]
enum AnnouncableActivities {
Create,
Update,
Like,
Dislike,
Delete,
Remove,
Undo,
}
/// Takes an announce and passes the inner activity to the appropriate handler. /// Takes an announce and passes the inner activity to the appropriate handler.
pub async fn receive_announce( async fn receive_announce(
context: &LemmyContext, context: &LemmyContext,
activity: AnyBase, activity: AnyBase,
actor: &dyn ActorType, actor: &dyn ActorType,
@ -265,10 +246,7 @@ pub async fn receive_announce(
verify_activity_domains_valid(&announce, &actor.actor_id(), false)?; verify_activity_domains_valid(&announce, &actor.actor_id(), false)?;
is_addressed_to_public(&announce)?; is_addressed_to_public(&announce)?;
let kind = announce let kind = announce.object().as_single_kind_str();
.object()
.as_single_kind_str()
.and_then(|s| s.parse().ok());
let inner_activity = announce let inner_activity = announce
.object() .object()
.to_owned() .to_owned()
@ -281,23 +259,22 @@ pub async fn receive_announce(
return Ok(()); return Ok(());
} }
use AnnouncableActivities::*;
match kind { match kind {
Some(Create) => { Some("Create") => {
receive_create_for_community(context, inner_activity, &inner_id, request_counter).await 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 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 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 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("Delete") => receive_delete_for_community(context, inner_activity, &inner_id).await,
Some(Remove) => receive_remove_for_community(context, inner_activity, &inner_id).await, Some("Remove") => receive_remove_for_community(context, inner_activity, &inner_id).await,
Some(Undo) => { Some("Undo") => {
receive_undo_for_community(context, inner_activity, &inner_id, request_counter).await receive_undo_for_community(context, inner_activity, &inner_id, request_counter).await
} }
_ => receive_unhandled_activity(inner_activity), _ => receive_unhandled_activity(inner_activity),

View file

@ -8,7 +8,6 @@ pub mod fetcher;
pub mod http; pub mod http;
pub mod inbox; pub mod inbox;
pub mod objects; pub mod objects;
pub mod routes;
use crate::extensions::{ use crate::extensions::{
group_extensions::GroupExtension, group_extensions::GroupExtension,
@ -24,20 +23,17 @@ use activitystreams::{
use activitystreams_ext::{Ext1, Ext2}; use activitystreams_ext::{Ext1, Ext2};
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use diesel::NotFound; use diesel::NotFound;
use lemmy_api_structs::blocking;
use lemmy_db_queries::{source::activity::Activity_, ApubObject, DbPool}; use lemmy_db_queries::{source::activity::Activity_, ApubObject, DbPool};
use lemmy_db_schema::{ use lemmy_db_schema::source::{
source::{ activity::Activity,
activity::Activity, comment::Comment,
comment::Comment, community::Community,
community::Community, post::Post,
person::Person as DbPerson, private_message::PrivateMessage,
post::Post, user::User_,
private_message::PrivateMessage,
},
DbUrl,
}; };
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError}; use lemmy_structs::blocking;
use lemmy_utils::{location_info, settings::Settings, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::Serialize; use serde::Serialize;
use std::net::IpAddr; use std::net::IpAddr;
@ -45,7 +41,7 @@ use url::{ParseError, Url};
/// Activitystreams type for community /// Activitystreams type for community
type GroupExt = Ext2<ApActor<ApObject<Group>>, GroupExtension, PublicKeyExtension>; type GroupExt = Ext2<ApActor<ApObject<Group>>, GroupExtension, PublicKeyExtension>;
/// Activitystreams type for person /// Activitystreams type for user
type PersonExt = Ext1<ApActor<ApObject<Person>>, PublicKeyExtension>; type PersonExt = Ext1<ApActor<ApObject<Person>>, PublicKeyExtension>;
/// Activitystreams type for post /// Activitystreams type for post
type PageExt = Ext1<ApObject<Page>, PageExtension>; type PageExt = Ext1<ApObject<Page>, PageExtension>;
@ -67,7 +63,7 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
let domain = apub_id.domain().context(location_info!())?.to_string(); let domain = apub_id.domain().context(location_info!())?.to_string();
let local_instance = settings.get_hostname_without_port()?; let local_instance = settings.get_hostname_without_port()?;
if !settings.federation().enabled { if !settings.federation.enabled {
return if domain == local_instance { return if domain == local_instance {
Ok(()) Ok(())
} else { } else {
@ -84,30 +80,29 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
let host = apub_id.host_str().context(location_info!())?; let host = apub_id.host_str().context(location_info!())?;
let host_as_ip = host.parse::<IpAddr>(); let host_as_ip = host.parse::<IpAddr>();
if host == "localhost" || host_as_ip.is_ok() { if host == "localhost" || host_as_ip.is_ok() {
return Err(anyhow!("invalid hostname {}: {}", host, apub_id).into()); return Err(anyhow!("invalid hostname: {:?}", host).into());
} }
if apub_id.scheme() != Settings::get().get_protocol_string() { if apub_id.scheme() != Settings::get().get_protocol_string() {
return Err(anyhow!("invalid apub id scheme {}: {}", apub_id.scheme(), apub_id).into()); return Err(anyhow!("invalid apub id scheme: {:?}", apub_id.scheme()).into());
} }
let allowed_instances = Settings::get().get_allowed_instances(); let mut allowed_instances = Settings::get().get_allowed_instances();
let blocked_instances = Settings::get().get_blocked_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(()) Ok(())
} else if let Some(mut allowed) = allowed_instances { } else if !allowed_instances.is_empty() {
// need to allow this explicitly because apub receive might contain objects from our local // 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. // instance. split is needed to remove the port in our federation test setup.
allowed.push(local_instance); allowed_instances.push(local_instance);
if allowed.contains(&domain) { if allowed_instances.contains(&domain) {
Ok(()) Ok(())
} else { } else {
Err(anyhow!("{} not in federation allowlist", domain).into()) Err(anyhow!("{} not in federation allowlist", domain).into())
} }
} else if let Some(blocked) = blocked_instances { } else if !blocked_instances.is_empty() {
if blocked.contains(&domain) { if blocked_instances.contains(&domain) {
Err(anyhow!("{} is in federation blocklist", domain).into()) Err(anyhow!("{} is in federation blocklist", domain).into())
} else { } else {
Ok(()) Ok(())
@ -121,45 +116,30 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
/// and actors in Lemmy. /// and actors in Lemmy.
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
pub trait ApubObjectType { pub trait ApubObjectType {
async fn send_create(&self, creator: &DbPerson, context: &LemmyContext) async fn send_create(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
-> Result<(), LemmyError>; async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
async fn send_update(&self, creator: &DbPerson, context: &LemmyContext) async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
-> Result<(), LemmyError>;
async fn send_delete(&self, creator: &DbPerson, context: &LemmyContext)
-> Result<(), LemmyError>;
async fn send_undo_delete( async fn send_undo_delete(
&self, &self,
creator: &DbPerson, creator: &User_,
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, context: &LemmyContext,
) -> Result<(), LemmyError>; ) -> 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)] #[async_trait::async_trait(?Send)]
pub trait ApubLikeableType { pub trait ApubLikeableType {
async fn send_like(&self, creator: &DbPerson, context: &LemmyContext) -> Result<(), LemmyError>; async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
async fn send_dislike( async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
&self, async fn send_undo_like(&self, creator: &User_, context: &LemmyContext)
creator: &DbPerson, -> Result<(), LemmyError>;
context: &LemmyContext,
) -> Result<(), LemmyError>;
async fn send_undo_like(
&self,
creator: &DbPerson,
context: &LemmyContext,
) -> Result<(), LemmyError>;
} }
/// Common methods provided by ActivityPub actors (community and person). Not all methods are /// Common methods provided by ActivityPub actors (community and user). Not all methods are
/// implemented by all actors. /// implemented by all actors.
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
pub trait ActorType { pub trait ActorType {
fn is_local(&self) -> bool;
fn actor_id(&self) -> Url; 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) // TODO: every actor should have a public key, so this shouldnt be an option (needs to be fixed in db)
@ -198,15 +178,32 @@ pub trait ActorType {
/// For a given community, returns the inboxes of all followers. /// For a given community, returns the inboxes of all followers.
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>; async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
fn get_shared_inbox_or_inbox_url(&self) -> Url; // TODO move these to the db rows
fn get_inbox_url(&self) -> Result<Url, ParseError> {
Url::parse(&format!("{}/inbox", &self.actor_id()))
}
/// Outbox URL is not generally used by Lemmy, so it can be generated on the fly (but only for fn get_shared_inbox_url(&self) -> Result<Url, LemmyError> {
/// local actors). let actor_id = self.actor_id();
fn get_outbox_url(&self) -> Result<Url, LemmyError> { let url = format!(
if !self.is_local() { "{}://{}{}/inbox",
return Err(anyhow!("get_outbox_url() called for remote actor").into()); &actor_id.scheme(),
} &actor_id.host_str().context(location_info!())?,
Ok(Url::parse(&format!("{}/outbox", &self.actor_id()))?) if let Some(port) = actor_id.port() {
format!(":{}", port)
} else {
"".to_string()
},
);
Ok(Url::parse(&url)?)
}
fn get_outbox_url(&self) -> Result<Url, ParseError> {
Url::parse(&format!("{}/outbox", &self.actor_id()))
}
fn get_followers_url(&self) -> Result<Url, ParseError> {
Url::parse(&format!("{}/followers", &self.actor_id()))
} }
fn get_public_key_ext(&self) -> Result<PublicKeyExtension, LemmyError> { fn get_public_key_ext(&self) -> Result<PublicKeyExtension, LemmyError> {
@ -221,61 +218,6 @@ pub trait ActorType {
} }
} }
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(),
&actor_id.host_str().context(location_info!())?,
if let Some(port) = actor_id.port() {
format!(":{}", port)
} else {
"".to_string()
},
);
Ok(Url::parse(&url)?.into())
}
/// Store a sent or received activity in the database, for logging purposes. These records are not /// Store a sent or received activity in the database, for logging purposes. These records are not
/// persistent. /// persistent.
pub(crate) async fn insert_activity<T>( pub(crate) async fn insert_activity<T>(
@ -288,7 +230,7 @@ pub(crate) async fn insert_activity<T>(
where where
T: Serialize + std::fmt::Debug + Send + 'static, T: Serialize + std::fmt::Debug + Send + 'static,
{ {
let ap_id = ap_id.to_owned().into(); let ap_id = ap_id.to_string();
blocking(pool, move |conn| { blocking(pool, move |conn| {
Activity::insert(conn, ap_id, &activity, local, sensitive) Activity::insert(conn, ap_id, &activity, local, sensitive)
}) })
@ -297,8 +239,8 @@ where
} }
pub(crate) enum PostOrComment { pub(crate) enum PostOrComment {
Comment(Box<Comment>), Comment(Comment),
Post(Box<Post>), Post(Post),
} }
/// Tries to find a post or comment in the local database, without any network requests. /// Tries to find a post or comment in the local database, without any network requests.
@ -314,7 +256,7 @@ pub(crate) async fn find_post_or_comment_by_id(
}) })
.await?; .await?;
if let Ok(p) = post { if let Ok(p) = post {
return Ok(PostOrComment::Post(Box::new(p))); return Ok(PostOrComment::Post(p));
} }
let ap_id = apub_id.clone(); let ap_id = apub_id.clone();
@ -323,18 +265,18 @@ pub(crate) async fn find_post_or_comment_by_id(
}) })
.await?; .await?;
if let Ok(c) = comment { if let Ok(c) = comment {
return Ok(PostOrComment::Comment(Box::new(c))); return Ok(PostOrComment::Comment(c));
} }
Err(NotFound.into()) Err(NotFound.into())
} }
pub(crate) enum Object { pub(crate) enum Object {
Comment(Box<Comment>), Comment(Comment),
Post(Box<Post>), Post(Post),
Community(Box<Community>), Community(Community),
Person(Box<DbPerson>), User(User_),
PrivateMessage(Box<PrivateMessage>), PrivateMessage(PrivateMessage),
} }
pub(crate) async fn find_object_by_id( pub(crate) async fn find_object_by_id(
@ -344,18 +286,18 @@ pub(crate) async fn find_object_by_id(
let ap_id = apub_id.clone(); let ap_id = apub_id.clone();
if let Ok(pc) = find_post_or_comment_by_id(context, ap_id.to_owned()).await { if let Ok(pc) = find_post_or_comment_by_id(context, ap_id.to_owned()).await {
return Ok(match pc { return Ok(match pc {
PostOrComment::Post(p) => Object::Post(Box::new(*p)), PostOrComment::Post(p) => Object::Post(p),
PostOrComment::Comment(c) => Object::Comment(Box::new(*c)), PostOrComment::Comment(c) => Object::Comment(c),
}); });
} }
let ap_id = apub_id.clone(); let ap_id = apub_id.clone();
let person = blocking(context.pool(), move |conn| { let user = blocking(context.pool(), move |conn| {
DbPerson::read_from_apub_id(conn, &ap_id.into()) User_::read_from_apub_id(conn, &ap_id.into())
}) })
.await?; .await?;
if let Ok(u) = person { if let Ok(u) = user {
return Ok(Object::Person(Box::new(u))); return Ok(Object::User(u));
} }
let ap_id = apub_id.clone(); let ap_id = apub_id.clone();
@ -364,7 +306,7 @@ pub(crate) async fn find_object_by_id(
}) })
.await?; .await?;
if let Ok(c) = community { if let Ok(c) = community {
return Ok(Object::Community(Box::new(c))); return Ok(Object::Community(c));
} }
let private_message = blocking(context.pool(), move |conn| { let private_message = blocking(context.pool(), move |conn| {
@ -372,7 +314,7 @@ pub(crate) async fn find_object_by_id(
}) })
.await?; .await?;
if let Ok(pm) = private_message { if let Ok(pm) = private_message {
return Ok(Object::PrivateMessage(Box::new(pm))); return Ok(Object::PrivateMessage(pm));
} }
Err(NotFound.into()) Err(NotFound.into())

View file

@ -6,7 +6,7 @@ use crate::{
check_object_for_community_or_site_ban, check_object_for_community_or_site_ban,
create_tombstone, create_tombstone,
get_object_from_apub, get_object_from_apub,
get_or_fetch_and_upsert_person, get_or_fetch_and_upsert_user,
get_source_markdown_value, get_source_markdown_value,
set_content_and_source, set_content_and_source,
FromApub, FromApub,
@ -18,19 +18,16 @@ use crate::{
use activitystreams::{ use activitystreams::{
object::{kind::NoteType, ApObject, Note, Tombstone}, object::{kind::NoteType, ApObject, Note, Tombstone},
prelude::*, prelude::*,
public,
}; };
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use lemmy_api_structs::blocking;
use lemmy_db_queries::{Crud, DbPool}; use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::{ use lemmy_db_schema::source::{
source::{ comment::{Comment, CommentForm},
comment::{Comment, CommentForm}, community::Community,
person::Person, post::Post,
post::Post, user::User_,
},
CommentId,
}; };
use lemmy_structs::blocking;
use lemmy_utils::{ use lemmy_utils::{
location_info, location_info,
utils::{convert_datetime, remove_slurs}, utils::{convert_datetime, remove_slurs},
@ -47,11 +44,14 @@ impl ToApub for Comment {
let mut comment = ApObject::new(Note::new()); let mut comment = ApObject::new(Note::new());
let creator_id = self.creator_id; let creator_id = self.creator_id;
let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??; let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
let post_id = self.post_id; let post_id = self.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; 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 // Add a vector containing some important info to the "in_reply_to" field
// [post_ap_id, Option(parent_comment_ap_id)] // [post_ap_id, Option(parent_comment_ap_id)]
let mut in_reply_to_vec = vec![post.ap_id.into_inner()]; let mut in_reply_to_vec = vec![post.ap_id.into_inner()];
@ -67,7 +67,7 @@ impl ToApub for Comment {
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(self.ap_id.to_owned().into_inner()) .set_id(self.ap_id.to_owned().into_inner())
.set_published(convert_datetime(self.published)) .set_published(convert_datetime(self.published))
.set_to(public()) .set_to(community.actor_id.into_inner())
.set_many_in_reply_tos(in_reply_to_vec) .set_many_in_reply_tos(in_reply_to_vec)
.set_attributed_to(creator.actor_id.into_inner()); .set_attributed_to(creator.actor_id.into_inner());
@ -103,13 +103,13 @@ impl FromApub for Comment {
expected_domain: Url, expected_domain: Url,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<Comment, LemmyError> { ) -> Result<Comment, LemmyError> {
check_object_for_community_or_site_ban(note, context, request_counter).await?;
let comment: Comment = let comment: Comment =
get_object_from_apub(note, context, expected_domain, request_counter).await?; get_object_from_apub(note, context, expected_domain, request_counter).await?;
let post_id = comment.post_id; let post_id = comment.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
check_object_for_community_or_site_ban(note, post.community_id, context, request_counter)
.await?;
if post.locked { if post.locked {
// This is not very efficient because a comment gets inserted just to be deleted right // 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. // afterwards, but it seems to be the easiest way to implement it.
@ -138,8 +138,7 @@ impl FromApubToForm<NoteExt> for CommentForm {
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let creator = let creator = get_or_fetch_and_upsert_user(creator_actor_id, context, request_counter).await?;
get_or_fetch_and_upsert_person(creator_actor_id, context, request_counter).await?;
let mut in_reply_tos = note let mut in_reply_tos = note
.in_reply_to() .in_reply_to()
@ -156,7 +155,7 @@ impl FromApubToForm<NoteExt> for CommentForm {
// The 2nd item, if it exists, is the parent comment apub_id // The 2nd item, if it exists, is the parent comment apub_id
// For deeply nested comments, FromApub automatically gets called recursively // For deeply nested comments, FromApub automatically gets called recursively
let parent_id: Option<CommentId> = match in_reply_tos.next() { let parent_id: Option<i32> = match in_reply_tos.next() {
Some(parent_comment_uri) => { Some(parent_comment_uri) => {
let parent_comment_ap_id = &parent_comment_uri?; let parent_comment_ap_id = &parent_comment_uri?;
let parent_comment = let parent_comment =

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
extensions::{context::lemmy_context, group_extensions::GroupExtension}, extensions::{context::lemmy_context, group_extensions::GroupExtension},
fetcher::person::get_or_fetch_and_upsert_person, fetcher::user::get_or_fetch_and_upsert_user,
objects::{ objects::{
check_object_domain, check_object_domain,
create_tombstone, create_tombstone,
@ -22,13 +22,13 @@ use activitystreams::{
}; };
use activitystreams_ext::Ext2; use activitystreams_ext::Ext2;
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::blocking;
use lemmy_db_queries::DbPool; use lemmy_db_queries::DbPool;
use lemmy_db_schema::{ use lemmy_db_schema::{
naive_now, naive_now,
source::community::{Community, CommunityForm}, source::community::{Community, CommunityForm},
}; };
use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView; use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
use lemmy_structs::blocking;
use lemmy_utils::{ use lemmy_utils::{
location_info, location_info,
utils::{check_slurs, check_slurs_opt, convert_datetime}, utils::{check_slurs, check_slurs_opt, convert_datetime},
@ -73,29 +73,36 @@ impl ToApub for Community {
if let Some(icon_url) = &self.icon { if let Some(icon_url) = &self.icon {
let mut image = Image::new(); let mut image = Image::new();
image.set_url::<Url>(icon_url.to_owned().into()); image.set_url(Url::parse(icon_url)?);
group.set_icon(image.into_any_base()?); group.set_icon(image.into_any_base()?);
} }
if let Some(banner_url) = &self.banner { if let Some(banner_url) = &self.banner {
let mut image = Image::new(); let mut image = Image::new();
image.set_url::<Url>(banner_url.to_owned().into()); image.set_url(Url::parse(banner_url)?);
group.set_image(image.into_any_base()?); group.set_image(image.into_any_base()?);
} }
let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), group); let mut ap_actor = ApActor::new(self.get_inbox_url()?, group);
ap_actor ap_actor
.set_preferred_username(self.name.to_owned()) .set_preferred_username(self.name.to_owned())
.set_outbox(self.get_outbox_url()?) .set_outbox(self.get_outbox_url()?)
.set_followers(self.followers_url.clone().into()) .set_followers(self.get_followers_url()?)
.set_endpoints(Endpoints { .set_endpoints(Endpoints {
shared_inbox: Some(self.get_shared_inbox_or_inbox_url()), shared_inbox: Some(self.get_shared_inbox_url()?),
..Default::default() ..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( Ok(Ext2::new(
ap_actor, ap_actor,
GroupExtension::new(self.nsfw)?, group_extension,
self.get_public_key_ext()?, self.get_public_key_ext()?,
)) ))
} }
@ -143,7 +150,7 @@ impl FromApubToForm<GroupExt> for CommunityForm {
.as_xsd_any_uri() .as_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let creator = get_or_fetch_and_upsert_person(creator_uri, context, request_counter).await?; let creator = get_or_fetch_and_upsert_user(creator_uri, context, request_counter).await?;
let name = group let name = group
.inner .inner
.preferred_username() .preferred_username()
@ -173,10 +180,11 @@ impl FromApubToForm<GroupExt> for CommunityForm {
.url() .url()
.context(location_info!())? .context(location_info!())?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.map(|u| u.to_owned().into()), .map(|u| u.to_string()),
), ),
None => None, None => None,
}; };
let banner = match group.image() { let banner = match group.image() {
Some(any_image) => Some( Some(any_image) => Some(
Image::from_any_base(any_image.as_one().context(location_info!())?.clone()) Image::from_any_base(any_image.as_one().context(location_info!())?.clone())
@ -185,27 +193,22 @@ impl FromApubToForm<GroupExt> for CommunityForm {
.url() .url()
.context(location_info!())? .context(location_info!())?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.map(|u| u.to_owned().into()), .map(|u| u.to_string()),
), ),
None => None, None => None,
}; };
let shared_inbox = group
.inner
.endpoints()?
.map(|e| e.shared_inbox)
.flatten()
.map(|s| s.to_owned().into());
Ok(CommunityForm { Ok(CommunityForm {
name, name,
title, title,
description, description,
category_id: group.ext_one.category.identifier.parse::<i32>()?,
creator_id: creator.id, creator_id: creator.id,
removed: None, removed: None,
published: group.inner.published().map(|u| u.to_owned().naive_local()), published: group.inner.published().map(|u| u.to_owned().naive_local()),
updated: group.inner.updated().map(|u| u.to_owned().naive_local()), updated: group.inner.updated().map(|u| u.to_owned().naive_local()),
deleted: None, deleted: None,
nsfw: group.ext_one.sensitive.unwrap_or(false), nsfw: group.ext_one.sensitive,
actor_id: Some(check_object_domain(group, expected_domain)?), actor_id: Some(check_object_domain(group, expected_domain)?),
local: false, local: false,
private_key: None, private_key: None,
@ -213,16 +216,6 @@ impl FromApubToForm<GroupExt> for CommunityForm {
last_refreshed_at: Some(naive_now()), last_refreshed_at: Some(naive_now()),
icon, icon,
banner, 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),
}) })
} }
} }

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
check_is_apub_id_valid, check_is_apub_id_valid,
fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, fetcher::{community::get_or_fetch_and_upsert_community, user::get_or_fetch_and_upsert_user},
inbox::community_inbox::check_community_or_site_ban, inbox::community_inbox::check_community_or_site_ban,
}; };
use activitystreams::{ use activitystreams::{
@ -11,24 +11,17 @@ use activitystreams::{
}; };
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking;
use lemmy_db_queries::{ApubObject, Crud, DbPool}; use lemmy_db_queries::{ApubObject, Crud, DbPool};
use lemmy_db_schema::{source::community::Community, CommunityId, DbUrl}; use lemmy_structs::blocking;
use lemmy_utils::{ use lemmy_utils::{location_info, settings::Settings, utils::convert_datetime, LemmyError};
location_info,
settings::structs::Settings,
utils::{convert_datetime, markdown_to_html},
LemmyError,
};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use url::Url; use url::Url;
pub(crate) mod comment; pub(crate) mod comment;
pub(crate) mod community; pub(crate) mod community;
pub(crate) mod person;
pub(crate) mod post; pub(crate) mod post;
pub(crate) mod private_message; pub(crate) mod private_message;
pub(crate) mod user;
/// Trait for converting an object or actor into the respective ActivityPub type. /// Trait for converting an object or actor into the respective ActivityPub type.
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
@ -96,7 +89,7 @@ where
pub(in crate::objects) fn check_object_domain<T, Kind>( pub(in crate::objects) fn check_object_domain<T, Kind>(
apub: &T, apub: &T,
expected_domain: Url, expected_domain: Url,
) -> Result<DbUrl, LemmyError> ) -> Result<lemmy_db_schema::Url, LemmyError>
where where
T: Base + AsBase<Kind>, T: Base + AsBase<Kind>,
{ {
@ -119,8 +112,11 @@ where
.set_media_type(mime_markdown()?); .set_media_type(mime_markdown()?);
object.set_source(source.into_any_base()?); object.set_source(source.into_any_base()?);
object.set_content(markdown_to_html(markdown_text)); // set `content` to markdown for compatibility with older Lemmy versions
object.set_media_type(mime_html()?); // 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));
Ok(()) Ok(())
} }
@ -136,28 +132,32 @@ where
.flatten() .flatten()
.map(|s| s.to_string()); .map(|s| s.to_string());
if content.is_some() { if content.is_some() {
let source = object.source().context(location_info!())?; let source = object.source();
let source = Object::<()>::from_any_base(source.to_owned())?.context(location_info!())?; // updated lemmy version, read markdown from `source.content`
check_is_markdown(source.media_type())?; if let Some(source) = source {
let source_content = source let source = Object::<()>::from_any_base(source.to_owned())?.context(location_info!())?;
.content() check_is_markdown(source.media_type())?;
.map(|s| s.as_single_xsd_string()) let source_content = source
.flatten() .content()
.context(location_info!())? .map(|s| s.as_single_xsd_string())
.to_string(); .flatten()
return Ok(Some(source_content)); .context(location_info!())?
.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) Ok(None)
} }
fn mime_markdown() -> Result<Mime, FromStrError> { pub(in crate::objects) fn mime_markdown() -> Result<Mime, FromStrError> {
"text/markdown".parse() "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> { pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), LemmyError> {
let mime = mime.context(location_info!())?; let mime = mime.context(location_info!())?;
if !mime.eq(&mime_markdown()?) { 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 /// 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 /// with the same ActivityPub ID already exists in the database, it is returned directly. Otherwise
/// the apub object is parsed, inserted and returned. /// the apub object is parsed, inserted and returned.
pub(in crate::objects) async fn get_object_from_apub<From, Kind, To, ToForm, IdType>( pub(in crate::objects) async fn get_object_from_apub<From, Kind, To, ToForm>(
from: &From, from: &From,
context: &LemmyContext, context: &LemmyContext,
expected_domain: Url, expected_domain: Url,
@ -180,14 +180,14 @@ pub(in crate::objects) async fn get_object_from_apub<From, Kind, To, ToForm, IdT
) -> Result<To, LemmyError> ) -> Result<To, LemmyError>
where where
From: BaseExt<Kind>, From: BaseExt<Kind>,
To: ApubObject<ToForm> + Crud<ToForm, IdType> + Send + 'static, To: ApubObject<ToForm> + Crud<ToForm> + Send + 'static,
ToForm: FromApubToForm<From> + Send + 'static, ToForm: FromApubToForm<From> + Send + 'static,
{ {
let object_id = from.id_unchecked().context(location_info!())?.to_owned(); let object_id = from.id_unchecked().context(location_info!())?.to_owned();
let domain = object_id.domain().context(location_info!())?; let domain = object_id.domain().context(location_info!())?;
// if its a local object, return it directly from the database // 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| { let object = blocking(context.pool(), move |conn| {
To::read_from_apub_id(conn, &object_id.into()) To::read_from_apub_id(conn, &object_id.into())
}) })
@ -205,43 +205,23 @@ where
pub(in crate::objects) async fn check_object_for_community_or_site_ban<T, Kind>( pub(in crate::objects) async fn check_object_for_community_or_site_ban<T, Kind>(
object: &T, object: &T,
community_id: CommunityId,
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> ) -> Result<(), LemmyError>
where where
T: ObjectExt<Kind>, T: ObjectExt<Kind>,
{ {
let person_id = object let user_id = object
.attributed_to() .attributed_to()
.context(location_info!())? .context(location_info!())?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?; let user = get_or_fetch_and_upsert_user(user_id, context, request_counter).await?;
check_community_or_site_ban(&person, community_id, context.pool()).await let community_id = object
}
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() .to()
.context(location_info!())? .context(location_info!())?
.as_many() .as_single_xsd_any_uri()
.context(location_info!())? .context(location_info!())?;
.iter() let community = get_or_fetch_and_upsert_community(community_id, context, request_counter).await?;
.map(|a| a.as_xsd_any_uri().context(location_info!())) check_community_or_site_ban(&user, &community, context.pool()).await
.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())
} }

View file

@ -1,13 +1,12 @@
use crate::{ use crate::{
extensions::{context::lemmy_context, page_extension::PageExtension}, extensions::{context::lemmy_context, page_extension::PageExtension},
fetcher::person::get_or_fetch_and_upsert_person, fetcher::{community::get_or_fetch_and_upsert_community, user::get_or_fetch_and_upsert_user},
objects::{ objects::{
check_object_domain, check_object_domain,
check_object_for_community_or_site_ban, check_object_for_community_or_site_ban,
create_tombstone, create_tombstone,
get_object_from_apub, get_object_from_apub,
get_source_markdown_value, get_source_markdown_value,
get_to_community,
set_content_and_source, set_content_and_source,
FromApub, FromApub,
FromApubToForm, FromApubToForm,
@ -18,20 +17,16 @@ use crate::{
use activitystreams::{ use activitystreams::{
object::{kind::PageType, ApObject, Image, Page, Tombstone}, object::{kind::PageType, ApObject, Image, Page, Tombstone},
prelude::*, prelude::*,
public,
}; };
use activitystreams_ext::Ext1; use activitystreams_ext::Ext1;
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::blocking;
use lemmy_db_queries::{Crud, DbPool}; use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::{ use lemmy_db_schema::source::{
self, community::Community,
source::{ post::{Post, PostForm},
community::Community, user::User_,
person::Person,
post::{Post, PostForm},
},
}; };
use lemmy_structs::blocking;
use lemmy_utils::{ use lemmy_utils::{
location_info, location_info,
request::fetch_iframely_and_pictrs_data, request::fetch_iframely_and_pictrs_data,
@ -50,7 +45,7 @@ impl ToApub for Post {
let mut page = ApObject::new(Page::new()); let mut page = ApObject::new(Page::new());
let creator_id = self.creator_id; let creator_id = self.creator_id;
let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??; let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
let community_id = self.community_id; let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
@ -61,25 +56,27 @@ impl ToApub for Post {
// https://git.asonix.dog/Aardwolf/activitystreams/issues/5 // https://git.asonix.dog/Aardwolf/activitystreams/issues/5
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
.set_id(self.ap_id.to_owned().into_inner()) .set_id(self.ap_id.to_owned().into_inner())
.set_name(self.name.to_owned()) // Use summary field to be consistent with mastodon content warning.
// `summary` field for compatibility with lemmy v0.9.9 and older, // https://mastodon.xyz/@Louisa/103987265222901387.json
// TODO: remove this after some time
.set_summary(self.name.to_owned()) .set_summary(self.name.to_owned())
.set_published(convert_datetime(self.published)) .set_published(convert_datetime(self.published))
.set_many_tos(vec![community.actor_id.into_inner(), public()]) .set_to(community.actor_id.into_inner())
.set_attributed_to(creator.actor_id.into_inner()); .set_attributed_to(creator.actor_id.into_inner());
if let Some(body) = &self.body { if let Some(body) = &self.body {
set_content_and_source(&mut page, &body)?; set_content_and_source(&mut page, &body)?;
} }
if let Some(url) = &self.url { // TODO: hacky code because we get self.url == Some("")
page.set_url::<Url>(url.to_owned().into()); // 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(thumbnail_url) = &self.thumbnail_url { if let Some(thumbnail_url) = &self.thumbnail_url {
let mut image = Image::new(); let mut image = Image::new();
image.set_url::<Url>(thumbnail_url.to_owned().into()); image.set_url(Url::parse(thumbnail_url)?);
page.set_image(image.into_any_base()?); page.set_image(image.into_any_base()?);
} }
@ -88,9 +85,9 @@ impl ToApub for Post {
} }
let ext = PageExtension { let ext = PageExtension {
comments_enabled: Some(!self.locked), comments_enabled: !self.locked,
sensitive: Some(self.nsfw), sensitive: self.nsfw,
stickied: Some(self.stickied), stickied: self.stickied,
}; };
Ok(Ext1::new(page, ext)) Ok(Ext1::new(page, ext))
} }
@ -118,10 +115,8 @@ impl FromApub for Post {
expected_domain: Url, expected_domain: Url,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<Post, LemmyError> { ) -> Result<Post, LemmyError> {
let post: Post = get_object_from_apub(page, context, expected_domain, request_counter).await?; check_object_for_community_or_site_ban(page, context, request_counter).await?;
check_object_for_community_or_site_ban(page, post.community_id, context, request_counter) get_object_from_apub(page, context, expected_domain, request_counter).await
.await?;
Ok(post)
} }
} }
@ -142,12 +137,20 @@ impl FromApubToForm<PageExt> for PostForm {
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let creator = let creator = get_or_fetch_and_upsert_user(creator_actor_id, context, request_counter).await?;
get_or_fetch_and_upsert_person(creator_actor_id, context, request_counter).await?;
let community = get_to_community(page, 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 thumbnail_url: Option<Url> = match &page.inner.image() { let community =
get_or_fetch_and_upsert_community(community_actor_id, context, request_counter).await?;
let thumbnail_url = match &page.inner.image() {
Some(any_image) => Image::from_any_base( Some(any_image) => Image::from_any_base(
any_image any_image
.to_owned() .to_owned()
@ -159,7 +162,7 @@ impl FromApubToForm<PageExt> for PostForm {
.url() .url()
.context(location_info!())? .context(location_info!())?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.map(|url| url.to_owned()), .map(|u| u.to_string()),
None => None, None => None,
}; };
let url = page let url = page
@ -167,22 +170,19 @@ impl FromApubToForm<PageExt> for PostForm {
.url() .url()
.map(|u| u.as_single_xsd_any_uri()) .map(|u| u.as_single_xsd_any_uri())
.flatten() .flatten()
.map(|u| u.to_owned()); .map(|s| s.to_string());
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
if let Some(url) = &url { if let Some(url) = &url {
fetch_iframely_and_pictrs_data(context.client(), Some(url)).await fetch_iframely_and_pictrs_data(context.client(), Some(url.to_owned())).await
} else { } else {
(None, None, None, thumbnail_url) (None, None, None, thumbnail_url)
}; };
let name = page let name = page
.inner .inner
.name() .summary()
.map(|s| s.map(|s2| s2.to_owned())) .as_ref()
// 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!())? .context(location_info!())?
.as_single_xsd_string() .as_single_xsd_string()
.context(location_info!())? .context(location_info!())?
@ -193,12 +193,12 @@ impl FromApubToForm<PageExt> for PostForm {
let body_slurs_removed = body.map(|b| remove_slurs(&b)); let body_slurs_removed = body.map(|b| remove_slurs(&b));
Ok(PostForm { Ok(PostForm {
name, name,
url: url.map(|u| u.into()), url,
body: body_slurs_removed, body: body_slurs_removed,
creator_id: creator.id, creator_id: creator.id,
community_id: community.id, community_id: community.id,
removed: None, removed: None,
locked: ext.comments_enabled.map(|e| !e), locked: Some(!ext.comments_enabled),
published: page published: page
.inner .inner
.published() .published()
@ -210,12 +210,12 @@ impl FromApubToForm<PageExt> for PostForm {
.as_ref() .as_ref()
.map(|u| u.to_owned().naive_local()), .map(|u| u.to_owned().naive_local()),
deleted: None, deleted: None,
nsfw: ext.sensitive.unwrap_or(false), nsfw: ext.sensitive,
stickied: ext.stickied.or(Some(false)), stickied: Some(ext.stickied),
embed_title: iframely_title, embed_title: iframely_title,
embed_description: iframely_description, embed_description: iframely_description,
embed_html: iframely_html, embed_html: iframely_html,
thumbnail_url: pictrs_thumbnail.map(|u| u.into()), thumbnail_url: pictrs_thumbnail,
ap_id: Some(check_object_domain(page, expected_domain)?), ap_id: Some(check_object_domain(page, expected_domain)?),
local: false, local: false,
}) })

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
check_is_apub_id_valid, check_is_apub_id_valid,
extensions::context::lemmy_context, extensions::context::lemmy_context,
fetcher::person::get_or_fetch_and_upsert_person, fetcher::user::get_or_fetch_and_upsert_user,
objects::{ objects::{
check_object_domain, check_object_domain,
create_tombstone, create_tombstone,
@ -19,12 +19,12 @@ use activitystreams::{
prelude::*, prelude::*,
}; };
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::blocking;
use lemmy_db_queries::{Crud, DbPool}; use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
person::Person,
private_message::{PrivateMessage, PrivateMessageForm}, private_message::{PrivateMessage, PrivateMessageForm},
user::User_,
}; };
use lemmy_structs::blocking;
use lemmy_utils::{location_info, utils::convert_datetime, LemmyError}; use lemmy_utils::{location_info, utils::convert_datetime, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use url::Url; use url::Url;
@ -37,10 +37,10 @@ impl ToApub for PrivateMessage {
let mut private_message = ApObject::new(Note::new()); let mut private_message = ApObject::new(Note::new());
let creator_id = self.creator_id; let creator_id = self.creator_id;
let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??; let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
let recipient_id = self.recipient_id; let recipient_id = self.recipient_id;
let recipient = blocking(pool, move |conn| Person::read(conn, recipient_id)).await??; let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
private_message private_message
.set_many_contexts(lemmy_context()?) .set_many_contexts(lemmy_context()?)
@ -97,8 +97,7 @@ impl FromApubToForm<NoteExt> for PrivateMessageForm {
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let creator = let creator = get_or_fetch_and_upsert_user(&creator_actor_id, context, request_counter).await?;
get_or_fetch_and_upsert_person(&creator_actor_id, context, request_counter).await?;
let recipient_actor_id = note let recipient_actor_id = note
.to() .to()
.context(location_info!())? .context(location_info!())?
@ -106,7 +105,7 @@ impl FromApubToForm<NoteExt> for PrivateMessageForm {
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let recipient = let recipient =
get_or_fetch_and_upsert_person(&recipient_actor_id, context, request_counter).await?; get_or_fetch_and_upsert_user(&recipient_actor_id, context, request_counter).await?;
let ap_id = note.id_unchecked().context(location_info!())?.to_string(); let ap_id = note.id_unchecked().context(location_info!())?.to_string();
check_is_apub_id_valid(&Url::parse(&ap_id)?)?; check_is_apub_id_valid(&Url::parse(&ap_id)?)?;

View file

@ -18,15 +18,15 @@ use activitystreams::{
}; };
use activitystreams_ext::Ext1; use activitystreams_ext::Ext1;
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::blocking;
use lemmy_db_queries::{ApubObject, DbPool}; use lemmy_db_queries::{ApubObject, DbPool};
use lemmy_db_schema::{ use lemmy_db_schema::{
naive_now, naive_now,
source::person::{Person as DbPerson, PersonForm}, source::user::{UserForm, User_},
}; };
use lemmy_structs::blocking;
use lemmy_utils::{ use lemmy_utils::{
location_info, location_info,
settings::structs::Settings, settings::Settings,
utils::{check_slurs, check_slurs_opt, convert_datetime}, utils::{check_slurs, check_slurs_opt, convert_datetime},
LemmyError, LemmyError,
}; };
@ -34,7 +34,7 @@ use lemmy_websocket::LemmyContext;
use url::Url; use url::Url;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl ToApub for DbPerson { impl ToApub for User_ {
type ApubType = PersonExt; type ApubType = PersonExt;
async fn to_apub(&self, _pool: &DbPool) -> Result<PersonExt, LemmyError> { async fn to_apub(&self, _pool: &DbPool) -> Result<PersonExt, LemmyError> {
@ -50,30 +50,33 @@ impl ToApub for DbPerson {
if let Some(avatar_url) = &self.avatar { if let Some(avatar_url) = &self.avatar {
let mut image = Image::new(); let mut image = Image::new();
image.set_url::<Url>(avatar_url.to_owned().into()); image.set_url(Url::parse(avatar_url)?);
person.set_icon(image.into_any_base()?); person.set_icon(image.into_any_base()?);
} }
if let Some(banner_url) = &self.banner { if let Some(banner_url) = &self.banner {
let mut image = Image::new(); let mut image = Image::new();
image.set_url::<Url>(banner_url.to_owned().into()); image.set_url(Url::parse(banner_url)?);
person.set_image(image.into_any_base()?); person.set_image(image.into_any_base()?);
} }
if let Some(bio) = &self.bio { if let Some(bio) = &self.bio {
set_content_and_source(&mut person, 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() { if let Some(i) = self.preferred_username.to_owned() {
person.set_name(i); person.set_name(i);
} }
let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), person); let mut ap_actor = ApActor::new(self.get_inbox_url()?, person);
ap_actor ap_actor
.set_preferred_username(self.name.to_owned()) .set_preferred_username(self.name.to_owned())
.set_outbox(self.get_outbox_url()?) .set_outbox(self.get_outbox_url()?)
.set_endpoints(Endpoints { .set_endpoints(Endpoints {
shared_inbox: Some(self.get_shared_inbox_or_inbox_url()), shared_inbox: Some(self.get_shared_inbox_url()?),
..Default::default() ..Default::default()
}); });
@ -85,7 +88,7 @@ impl ToApub for DbPerson {
} }
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl FromApub for DbPerson { impl FromApub for User_ {
type ApubType = PersonExt; type ApubType = PersonExt;
async fn from_apub( async fn from_apub(
@ -93,29 +96,26 @@ impl FromApub for DbPerson {
context: &LemmyContext, context: &LemmyContext,
expected_domain: Url, expected_domain: Url,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<DbPerson, LemmyError> { ) -> Result<User_, LemmyError> {
let person_id = person.id_unchecked().context(location_info!())?.to_owned(); let user_id = person.id_unchecked().context(location_info!())?.to_owned();
let domain = person_id.domain().context(location_info!())?; let domain = user_id.domain().context(location_info!())?;
if domain == Settings::get().hostname() { if domain == Settings::get().hostname {
let person = blocking(context.pool(), move |conn| { let user = blocking(context.pool(), move |conn| {
DbPerson::read_from_apub_id(conn, &person_id.into()) User_::read_from_apub_id(conn, &user_id.into())
}) })
.await??; .await??;
Ok(person) Ok(user)
} else { } else {
let person_form = let user_form =
PersonForm::from_apub(person, context, expected_domain, request_counter).await?; UserForm::from_apub(person, context, expected_domain, request_counter).await?;
let person = blocking(context.pool(), move |conn| { let user = blocking(context.pool(), move |conn| User_::upsert(conn, &user_form)).await??;
DbPerson::upsert(conn, &person_form) Ok(user)
})
.await??;
Ok(person)
} }
} }
} }
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl FromApubToForm<PersonExt> for PersonForm { impl FromApubToForm<PersonExt> for UserForm {
async fn from_apub( async fn from_apub(
person: &PersonExt, person: &PersonExt,
_context: &LemmyContext, _context: &LemmyContext,
@ -129,7 +129,7 @@ impl FromApubToForm<PersonExt> for PersonForm {
.url() .url()
.context(location_info!())? .context(location_info!())?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.map(|url| url.to_owned()), .map(|u| u.to_string()),
), ),
None => None, None => None,
}; };
@ -142,7 +142,7 @@ impl FromApubToForm<PersonExt> for PersonForm {
.url() .url()
.context(location_info!())? .context(location_info!())?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.map(|url| url.to_owned()), .map(|u| u.to_string()),
), ),
None => None, None => None,
}; };
@ -158,35 +158,38 @@ impl FromApubToForm<PersonExt> for PersonForm {
.flatten() .flatten()
.map(|n| n.to_owned().xsd_string()) .map(|n| n.to_owned().xsd_string())
.flatten(); .flatten();
let bio = get_source_markdown_value(person)?; 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(&name)?;
check_slurs_opt(&preferred_username)?; check_slurs_opt(&preferred_username)?;
check_slurs_opt(&bio)?; check_slurs_opt(&bio)?;
Ok(PersonForm { Ok(UserForm {
name, name,
preferred_username: Some(preferred_username), preferred_username: Some(preferred_username),
password_encrypted: "".to_string(),
admin: false,
banned: None, banned: None,
deleted: None, email: None,
avatar: avatar.map(|o| o.map(|i| i.into())), avatar,
banner: banner.map(|o| o.map(|i| i.into())), banner,
published: person.inner.published().map(|u| u.to_owned().naive_local()), published: person.inner.published().map(|u| u.to_owned().naive_local()),
updated: person.updated().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)?), actor_id: Some(check_object_domain(person, expected_domain)?),
bio: Some(bio), bio: Some(bio),
local: Some(false), local: false,
private_key: None, private_key: None,
public_key: Some(Some(person.ext_one.public_key.to_owned().public_key_pem)), public_key: Some(person.ext_one.public_key.to_owned().public_key_pem),
last_refreshed_at: Some(naive_now()), last_refreshed_at: Some(naive_now()),
inbox_url: Some(person.inner.inbox()?.to_owned().into()),
shared_inbox_url: Some(shared_inbox),
}) })
} }
} }

View file

@ -6,7 +6,6 @@ edition = "2018"
[lib] [lib]
name = "lemmy_db_queries" name = "lemmy_db_queries"
path = "src/lib.rs" path = "src/lib.rs"
doctest = false
[dependencies] [dependencies]
lemmy_utils = { path = "../utils" } lemmy_utils = { path = "../utils" }
@ -14,16 +13,13 @@ lemmy_db_schema = { path = "../db_schema" }
diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","serde_json"] } diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","serde_json"] }
diesel_migrations = "1.4.0" diesel_migrations = "1.4.0"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.118", features = ["derive"] }
serde_json = { version = "1.0.61", features = ["preserve_order"] } serde_json = { version = "1.0.60", features = ["preserve_order"] }
strum = "0.20.0" strum = "0.20.0"
strum_macros = "0.20.1" strum_macros = "0.20.1"
log = "0.4.14" log = "0.4.11"
sha2 = "0.9.3" sha2 = "0.9.2"
url = { version = "2.2.1", features = ["serde"] } url = { version = "2.2.0", features = ["serde"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
regex = "1.4.3" regex = "1.4.2"
bcrypt = "0.9.0" bcrypt = "0.9.0"
[dev-dependencies]
serial_test = "0.5.1"

View file

@ -1,12 +1,12 @@
use diesel::{result::Error, *}; use diesel::{result::Error, *};
use lemmy_db_schema::{schema::comment_aggregates, CommentId}; use lemmy_db_schema::schema::comment_aggregates;
use serde::Serialize; use serde::Serialize;
#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)] #[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
#[table_name = "comment_aggregates"] #[table_name = "comment_aggregates"]
pub struct CommentAggregates { pub struct CommentAggregates {
pub id: i32, pub id: i32,
pub comment_id: CommentId, pub comment_id: i32,
pub score: i64, pub score: i64,
pub upvotes: i64, pub upvotes: i64,
pub downvotes: i64, pub downvotes: i64,
@ -14,7 +14,7 @@ pub struct CommentAggregates {
} }
impl CommentAggregates { impl CommentAggregates {
pub fn read(conn: &PgConnection, comment_id: CommentId) -> Result<Self, Error> { pub fn read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
comment_aggregates::table comment_aggregates::table
.filter(comment_aggregates::comment_id.eq(comment_id)) .filter(comment_aggregates::comment_id.eq(comment_id))
.first::<Self>(conn) .first::<Self>(conn)
@ -28,67 +28,84 @@ mod tests {
establish_unpooled_connection, establish_unpooled_connection,
Crud, Crud,
Likeable, Likeable,
ListingType,
SortType,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
comment::{Comment, CommentForm, CommentLike, CommentLikeForm}, comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
community::{Community, CommunityForm}, community::{Community, CommunityForm},
person::{Person, PersonForm},
post::{Post, PostForm}, post::{Post, PostForm},
user::{UserForm, User_},
}; };
use serial_test::serial;
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let new_person = PersonForm { let new_user = UserForm {
name: "thommy_comment_agg".into(), name: "thommy_comment_agg".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_person = Person::create(&conn, &new_person).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let another_person = PersonForm { let another_user = UserForm {
name: "jerry_comment_agg".into(), name: "jerry_comment_agg".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let another_inserted_person = Person::create(&conn, &another_person).unwrap(); let another_inserted_user = User_::create(&conn, &another_user).unwrap();
let new_community = CommunityForm { let new_community = CommunityForm {
name: "TIL_comment_agg".into(), name: "TIL_comment_agg".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1,
nsfw: false, nsfw: false,
removed: None, removed: None,
deleted: None, deleted: None,
@ -101,9 +118,6 @@ mod tests {
published: None, published: None,
icon: None, icon: None,
banner: None, banner: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_community = Community::create(&conn, &new_community).unwrap(); let inserted_community = Community::create(&conn, &new_community).unwrap();
@ -112,7 +126,7 @@ mod tests {
name: "A test post".into(), name: "A test post".into(),
url: None, url: None,
body: None, body: None,
creator_id: inserted_person.id, creator_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -133,7 +147,7 @@ mod tests {
let comment_form = CommentForm { let comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -149,7 +163,7 @@ mod tests {
let child_comment_form = CommentForm { let child_comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -166,7 +180,7 @@ mod tests {
let comment_like = CommentLikeForm { let comment_like = CommentLikeForm {
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, user_id: inserted_user.id,
score: 1, score: 1,
}; };
@ -178,11 +192,11 @@ mod tests {
assert_eq!(1, comment_aggs_before_delete.upvotes); assert_eq!(1, comment_aggs_before_delete.upvotes);
assert_eq!(0, comment_aggs_before_delete.downvotes); assert_eq!(0, comment_aggs_before_delete.downvotes);
// Add a post dislike from the other person // Add a post dislike from the other user
let comment_dislike = CommentLikeForm { let comment_dislike = CommentLikeForm {
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: another_inserted_person.id, user_id: another_inserted_user.id,
score: -1, score: -1,
}; };
@ -195,7 +209,7 @@ mod tests {
assert_eq!(1, comment_aggs_after_dislike.downvotes); assert_eq!(1, comment_aggs_after_dislike.downvotes);
// Remove the first comment like // Remove the first comment like
CommentLike::remove(&conn, inserted_person.id, inserted_comment.id).unwrap(); CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
let after_like_remove = CommentAggregates::read(&conn, inserted_comment.id).unwrap(); let after_like_remove = CommentAggregates::read(&conn, inserted_comment.id).unwrap();
assert_eq!(-1, after_like_remove.score); assert_eq!(-1, after_like_remove.score);
assert_eq!(0, after_like_remove.upvotes); assert_eq!(0, after_like_remove.upvotes);
@ -209,8 +223,8 @@ mod tests {
assert!(after_delete.is_err()); assert!(after_delete.is_err());
// This should delete all the associated rows, and fire triggers // This should delete all the associated rows, and fire triggers
Person::delete(&conn, another_inserted_person.id).unwrap(); User_::delete(&conn, another_inserted_user.id).unwrap();
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap(); let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(1, person_num_deleted); assert_eq!(1, user_num_deleted);
} }
} }

View file

@ -1,24 +1,20 @@
use diesel::{result::Error, *}; use diesel::{result::Error, *};
use lemmy_db_schema::{schema::community_aggregates, CommunityId}; use lemmy_db_schema::schema::community_aggregates;
use serde::Serialize; use serde::Serialize;
#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)] #[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
#[table_name = "community_aggregates"] #[table_name = "community_aggregates"]
pub struct CommunityAggregates { pub struct CommunityAggregates {
pub id: i32, pub id: i32,
pub community_id: CommunityId, pub community_id: i32,
pub subscribers: i64, pub subscribers: i64,
pub posts: i64, pub posts: i64,
pub comments: i64, pub comments: i64,
pub published: chrono::NaiveDateTime, 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 { impl CommunityAggregates {
pub fn read(conn: &PgConnection, community_id: CommunityId) -> Result<Self, Error> { pub fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
community_aggregates::table community_aggregates::table
.filter(community_aggregates::community_id.eq(community_id)) .filter(community_aggregates::community_id.eq(community_id))
.first::<Self>(conn) .first::<Self>(conn)
@ -32,67 +28,84 @@ mod tests {
establish_unpooled_connection, establish_unpooled_connection,
Crud, Crud,
Followable, Followable,
ListingType,
SortType,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
comment::{Comment, CommentForm}, comment::{Comment, CommentForm},
community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm}, community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm},
person::{Person, PersonForm},
post::{Post, PostForm}, post::{Post, PostForm},
user::{UserForm, User_},
}; };
use serial_test::serial;
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let new_person = PersonForm { let new_user = UserForm {
name: "thommy_community_agg".into(), name: "thommy_community_agg".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_person = Person::create(&conn, &new_person).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let another_person = PersonForm { let another_user = UserForm {
name: "jerry_community_agg".into(), name: "jerry_community_agg".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let another_inserted_person = Person::create(&conn, &another_person).unwrap(); let another_inserted_user = User_::create(&conn, &another_user).unwrap();
let new_community = CommunityForm { let new_community = CommunityForm {
name: "TIL_community_agg".into(), name: "TIL_community_agg".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1,
nsfw: false, nsfw: false,
removed: None, removed: None,
deleted: None, deleted: None,
@ -105,18 +118,16 @@ mod tests {
published: None, published: None,
icon: None, icon: None,
banner: None, banner: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_community = Community::create(&conn, &new_community).unwrap(); let inserted_community = Community::create(&conn, &new_community).unwrap();
let another_community = CommunityForm { let another_community = CommunityForm {
name: "TIL_community_agg_2".into(), name: "TIL_community_agg_2".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1,
nsfw: false, nsfw: false,
removed: None, removed: None,
deleted: None, deleted: None,
@ -129,32 +140,29 @@ mod tests {
published: None, published: None,
icon: None, icon: None,
banner: None, banner: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let another_inserted_community = Community::create(&conn, &another_community).unwrap(); let another_inserted_community = Community::create(&conn, &another_community).unwrap();
let first_person_follow = CommunityFollowerForm { let first_user_follow = CommunityFollowerForm {
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: inserted_person.id, user_id: inserted_user.id,
pending: false, pending: false,
}; };
CommunityFollower::follow(&conn, &first_person_follow).unwrap(); CommunityFollower::follow(&conn, &first_user_follow).unwrap();
let second_person_follow = CommunityFollowerForm { let second_user_follow = CommunityFollowerForm {
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: another_inserted_person.id, user_id: another_inserted_user.id,
pending: false, pending: false,
}; };
CommunityFollower::follow(&conn, &second_person_follow).unwrap(); CommunityFollower::follow(&conn, &second_user_follow).unwrap();
let another_community_follow = CommunityFollowerForm { let another_community_follow = CommunityFollowerForm {
community_id: another_inserted_community.id, community_id: another_inserted_community.id,
person_id: inserted_person.id, user_id: inserted_user.id,
pending: false, pending: false,
}; };
@ -164,7 +172,7 @@ mod tests {
name: "A test post".into(), name: "A test post".into(),
url: None, url: None,
body: None, body: None,
creator_id: inserted_person.id, creator_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -185,7 +193,7 @@ mod tests {
let comment_form = CommentForm { let comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -201,7 +209,7 @@ mod tests {
let child_comment_form = CommentForm { let child_comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -230,12 +238,12 @@ mod tests {
assert_eq!(0, another_community_aggs.comments); assert_eq!(0, another_community_aggs.comments);
// Unfollow test // Unfollow test
CommunityFollower::unfollow(&conn, &second_person_follow).unwrap(); CommunityFollower::unfollow(&conn, &second_user_follow).unwrap();
let after_unfollow = CommunityAggregates::read(&conn, inserted_community.id).unwrap(); let after_unfollow = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
assert_eq!(1, after_unfollow.subscribers); assert_eq!(1, after_unfollow.subscribers);
// Follow again just for the later tests // Follow again just for the later tests
CommunityFollower::follow(&conn, &second_person_follow).unwrap(); CommunityFollower::follow(&conn, &second_user_follow).unwrap();
let after_follow_again = CommunityAggregates::read(&conn, inserted_community.id).unwrap(); let after_follow_again = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
assert_eq!(2, after_follow_again.subscribers); assert_eq!(2, after_follow_again.subscribers);
@ -245,14 +253,14 @@ mod tests {
assert_eq!(0, after_parent_post_delete.comments); assert_eq!(0, after_parent_post_delete.comments);
assert_eq!(0, after_parent_post_delete.posts); assert_eq!(0, after_parent_post_delete.posts);
// Remove the 2nd person // Remove the 2nd user
Person::delete(&conn, another_inserted_person.id).unwrap(); User_::delete(&conn, another_inserted_user.id).unwrap();
let after_person_delete = CommunityAggregates::read(&conn, inserted_community.id).unwrap(); let after_user_delete = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
assert_eq!(1, after_person_delete.subscribers); assert_eq!(1, after_user_delete.subscribers);
// This should delete all the associated rows, and fire triggers // This should delete all the associated rows, and fire triggers
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap(); let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(1, person_num_deleted); assert_eq!(1, user_num_deleted);
// Should be none found, since the creator was deleted // Should be none found, since the creator was deleted
let after_delete = CommunityAggregates::read(&conn, inserted_community.id); let after_delete = CommunityAggregates::read(&conn, inserted_community.id);

View file

@ -1,5 +1,5 @@
pub mod comment_aggregates; pub mod comment_aggregates;
pub mod community_aggregates; pub mod community_aggregates;
pub mod person_aggregates;
pub mod post_aggregates; pub mod post_aggregates;
pub mod site_aggregates; pub mod site_aggregates;
pub mod user_aggregates;

View file

@ -1,24 +1,23 @@
use diesel::{result::Error, *}; use diesel::{result::Error, *};
use lemmy_db_schema::{schema::post_aggregates, PostId}; use lemmy_db_schema::schema::post_aggregates;
use serde::Serialize; use serde::Serialize;
#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)] #[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
#[table_name = "post_aggregates"] #[table_name = "post_aggregates"]
pub struct PostAggregates { pub struct PostAggregates {
pub id: i32, pub id: i32,
pub post_id: PostId, pub post_id: i32,
pub comments: i64, pub comments: i64,
pub score: i64, pub score: i64,
pub upvotes: i64, pub upvotes: i64,
pub downvotes: i64, pub downvotes: i64,
pub stickied: bool, pub stickied: bool,
pub published: chrono::NaiveDateTime, 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, pub newest_comment_time: chrono::NaiveDateTime,
} }
impl PostAggregates { impl PostAggregates {
pub fn read(conn: &PgConnection, post_id: PostId) -> Result<Self, Error> { pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
post_aggregates::table post_aggregates::table
.filter(post_aggregates::post_id.eq(post_id)) .filter(post_aggregates::post_id.eq(post_id))
.first::<Self>(conn) .first::<Self>(conn)
@ -32,67 +31,84 @@ mod tests {
establish_unpooled_connection, establish_unpooled_connection,
Crud, Crud,
Likeable, Likeable,
ListingType,
SortType,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
comment::{Comment, CommentForm}, comment::{Comment, CommentForm},
community::{Community, CommunityForm}, community::{Community, CommunityForm},
person::{Person, PersonForm},
post::{Post, PostForm, PostLike, PostLikeForm}, post::{Post, PostForm, PostLike, PostLikeForm},
user::{UserForm, User_},
}; };
use serial_test::serial;
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let new_person = PersonForm { let new_user = UserForm {
name: "thommy_community_agg".into(), name: "thommy_community_agg".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_person = Person::create(&conn, &new_person).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let another_person = PersonForm { let another_user = UserForm {
name: "jerry_community_agg".into(), name: "jerry_community_agg".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let another_inserted_person = Person::create(&conn, &another_person).unwrap(); let another_inserted_user = User_::create(&conn, &another_user).unwrap();
let new_community = CommunityForm { let new_community = CommunityForm {
name: "TIL_community_agg".into(), name: "TIL_community_agg".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1,
nsfw: false, nsfw: false,
removed: None, removed: None,
deleted: None, deleted: None,
@ -105,9 +121,6 @@ mod tests {
published: None, published: None,
icon: None, icon: None,
banner: None, banner: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_community = Community::create(&conn, &new_community).unwrap(); let inserted_community = Community::create(&conn, &new_community).unwrap();
@ -116,7 +129,7 @@ mod tests {
name: "A test post".into(), name: "A test post".into(),
url: None, url: None,
body: None, body: None,
creator_id: inserted_person.id, creator_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -137,7 +150,7 @@ mod tests {
let comment_form = CommentForm { let comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -153,7 +166,7 @@ mod tests {
let child_comment_form = CommentForm { let child_comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -169,7 +182,7 @@ mod tests {
let post_like = PostLikeForm { let post_like = PostLikeForm {
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, user_id: inserted_user.id,
score: 1, score: 1,
}; };
@ -182,10 +195,10 @@ mod tests {
assert_eq!(1, post_aggs_before_delete.upvotes); assert_eq!(1, post_aggs_before_delete.upvotes);
assert_eq!(0, post_aggs_before_delete.downvotes); assert_eq!(0, post_aggs_before_delete.downvotes);
// Add a post dislike from the other person // Add a post dislike from the other user
let post_dislike = PostLikeForm { let post_dislike = PostLikeForm {
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: another_inserted_person.id, user_id: another_inserted_user.id,
score: -1, score: -1,
}; };
@ -207,7 +220,7 @@ mod tests {
assert_eq!(1, after_comment_delete.downvotes); assert_eq!(1, after_comment_delete.downvotes);
// Remove the first post like // Remove the first post like
PostLike::remove(&conn, inserted_person.id, inserted_post.id).unwrap(); PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
let after_like_remove = PostAggregates::read(&conn, inserted_post.id).unwrap(); let after_like_remove = PostAggregates::read(&conn, inserted_post.id).unwrap();
assert_eq!(0, after_like_remove.comments); assert_eq!(0, after_like_remove.comments);
assert_eq!(-1, after_like_remove.score); assert_eq!(-1, after_like_remove.score);
@ -215,9 +228,9 @@ mod tests {
assert_eq!(1, after_like_remove.downvotes); assert_eq!(1, after_like_remove.downvotes);
// This should delete all the associated rows, and fire triggers // This should delete all the associated rows, and fire triggers
Person::delete(&conn, another_inserted_person.id).unwrap(); User_::delete(&conn, another_inserted_user.id).unwrap();
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap(); let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(1, person_num_deleted); assert_eq!(1, user_num_deleted);
// Should be none found, since the creator was deleted // Should be none found, since the creator was deleted
let after_delete = PostAggregates::read(&conn, inserted_post.id); let after_delete = PostAggregates::read(&conn, inserted_post.id);

View file

@ -11,10 +11,6 @@ pub struct SiteAggregates {
pub posts: i64, pub posts: i64,
pub comments: i64, pub comments: i64,
pub communities: 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 { impl SiteAggregates {
@ -25,48 +21,60 @@ impl SiteAggregates {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{aggregates::site_aggregates::SiteAggregates, establish_unpooled_connection, Crud}; use crate::{
aggregates::site_aggregates::SiteAggregates,
establish_unpooled_connection,
Crud,
ListingType,
SortType,
};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
comment::{Comment, CommentForm}, comment::{Comment, CommentForm},
community::{Community, CommunityForm}, community::{Community, CommunityForm},
person::{Person, PersonForm},
post::{Post, PostForm}, post::{Post, PostForm},
site::{Site, SiteForm}, site::{Site, SiteForm},
user::{UserForm, User_},
}; };
use serial_test::serial;
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let new_person = PersonForm { let new_user = UserForm {
name: "thommy_site_agg".into(), name: "thommy_site_agg".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_person = Person::create(&conn, &new_person).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let site_form = SiteForm { let site_form = SiteForm {
name: "test_site".into(), name: "test_site".into(),
description: None, description: None,
icon: None, icon: None,
banner: None, banner: None,
creator_id: inserted_person.id, creator_id: inserted_user.id,
enable_downvotes: true, enable_downvotes: true,
open_registration: true, open_registration: true,
enable_nsfw: true, enable_nsfw: true,
@ -77,9 +85,10 @@ mod tests {
let new_community = CommunityForm { let new_community = CommunityForm {
name: "TIL_site_agg".into(), name: "TIL_site_agg".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1,
nsfw: false, nsfw: false,
removed: None, removed: None,
deleted: None, deleted: None,
@ -92,9 +101,6 @@ mod tests {
published: None, published: None,
icon: None, icon: None,
banner: None, banner: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_community = Community::create(&conn, &new_community).unwrap(); let inserted_community = Community::create(&conn, &new_community).unwrap();
@ -103,7 +109,7 @@ mod tests {
name: "A test post".into(), name: "A test post".into(),
url: None, url: None,
body: None, body: None,
creator_id: inserted_person.id, creator_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -126,7 +132,7 @@ mod tests {
let comment_form = CommentForm { let comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -143,7 +149,7 @@ mod tests {
let child_comment_form = CommentForm { let child_comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -171,8 +177,8 @@ mod tests {
assert_eq!(0, site_aggregates_after_post_delete.comments); assert_eq!(0, site_aggregates_after_post_delete.comments);
// This shouuld delete all the associated rows, and fire triggers // This shouuld delete all the associated rows, and fire triggers
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap(); let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(1, person_num_deleted); assert_eq!(1, user_num_deleted);
let after_delete = SiteAggregates::read(&conn); let after_delete = SiteAggregates::read(&conn);
assert!(after_delete.is_err()); assert!(after_delete.is_err());

View file

@ -1,22 +1,22 @@
use diesel::{result::Error, *}; use diesel::{result::Error, *};
use lemmy_db_schema::{schema::person_aggregates, PersonId}; use lemmy_db_schema::schema::user_aggregates;
use serde::Serialize; use serde::Serialize;
#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)] #[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
#[table_name = "person_aggregates"] #[table_name = "user_aggregates"]
pub struct PersonAggregates { pub struct UserAggregates {
pub id: i32, pub id: i32,
pub person_id: PersonId, pub user_id: i32,
pub post_count: i64, pub post_count: i64,
pub post_score: i64, pub post_score: i64,
pub comment_count: i64, pub comment_count: i64,
pub comment_score: i64, pub comment_score: i64,
} }
impl PersonAggregates { impl UserAggregates {
pub fn read(conn: &PgConnection, person_id: PersonId) -> Result<Self, Error> { pub fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
person_aggregates::table user_aggregates::table
.filter(person_aggregates::person_id.eq(person_id)) .filter(user_aggregates::user_id.eq(user_id))
.first::<Self>(conn) .first::<Self>(conn)
} }
} }
@ -24,71 +24,88 @@ impl PersonAggregates {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
aggregates::person_aggregates::PersonAggregates, aggregates::user_aggregates::UserAggregates,
establish_unpooled_connection, establish_unpooled_connection,
Crud, Crud,
Likeable, Likeable,
ListingType,
SortType,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
comment::{Comment, CommentForm, CommentLike, CommentLikeForm}, comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
community::{Community, CommunityForm}, community::{Community, CommunityForm},
person::{Person, PersonForm},
post::{Post, PostForm, PostLike, PostLikeForm}, post::{Post, PostForm, PostLike, PostLikeForm},
user::{UserForm, User_},
}; };
use serial_test::serial;
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let new_person = PersonForm { let new_user = UserForm {
name: "thommy_user_agg".into(), name: "thommy_user_agg".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_person = Person::create(&conn, &new_person).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let another_person = PersonForm { let another_user = UserForm {
name: "jerry_user_agg".into(), name: "jerry_user_agg".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let another_inserted_person = Person::create(&conn, &another_person).unwrap(); let another_inserted_user = User_::create(&conn, &another_user).unwrap();
let new_community = CommunityForm { let new_community = CommunityForm {
name: "TIL_site_agg".into(), name: "TIL_site_agg".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1,
nsfw: false, nsfw: false,
removed: None, removed: None,
deleted: None, deleted: None,
@ -101,9 +118,6 @@ mod tests {
published: None, published: None,
icon: None, icon: None,
banner: None, banner: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_community = Community::create(&conn, &new_community).unwrap(); let inserted_community = Community::create(&conn, &new_community).unwrap();
@ -112,7 +126,7 @@ mod tests {
name: "A test post".into(), name: "A test post".into(),
url: None, url: None,
body: None, body: None,
creator_id: inserted_person.id, creator_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -133,7 +147,7 @@ mod tests {
let post_like = PostLikeForm { let post_like = PostLikeForm {
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, user_id: inserted_user.id,
score: 1, score: 1,
}; };
@ -141,7 +155,7 @@ mod tests {
let comment_form = CommentForm { let comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -157,7 +171,7 @@ mod tests {
let mut comment_like = CommentLikeForm { let mut comment_like = CommentLikeForm {
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
person_id: inserted_person.id, user_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
score: 1, score: 1,
}; };
@ -166,7 +180,7 @@ mod tests {
let mut child_comment_form = CommentForm { let mut child_comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -182,29 +196,28 @@ mod tests {
let child_comment_like = CommentLikeForm { let child_comment_like = CommentLikeForm {
comment_id: inserted_child_comment.id, comment_id: inserted_child_comment.id,
person_id: another_inserted_person.id, user_id: another_inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
score: 1, score: 1,
}; };
let _inserted_child_comment_like = CommentLike::like(&conn, &child_comment_like).unwrap(); let _inserted_child_comment_like = CommentLike::like(&conn, &child_comment_like).unwrap();
let person_aggregates_before_delete = let user_aggregates_before_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
PersonAggregates::read(&conn, inserted_person.id).unwrap();
assert_eq!(1, person_aggregates_before_delete.post_count); assert_eq!(1, user_aggregates_before_delete.post_count);
assert_eq!(1, person_aggregates_before_delete.post_score); assert_eq!(1, user_aggregates_before_delete.post_score);
assert_eq!(2, person_aggregates_before_delete.comment_count); assert_eq!(2, user_aggregates_before_delete.comment_count);
assert_eq!(2, person_aggregates_before_delete.comment_score); assert_eq!(2, user_aggregates_before_delete.comment_score);
// Remove a post like // Remove a post like
PostLike::remove(&conn, inserted_person.id, inserted_post.id).unwrap(); PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
let after_post_like_remove = PersonAggregates::read(&conn, inserted_person.id).unwrap(); let after_post_like_remove = UserAggregates::read(&conn, inserted_user.id).unwrap();
assert_eq!(0, after_post_like_remove.post_score); assert_eq!(0, after_post_like_remove.post_score);
// Remove a parent comment (the scores should also be removed) // Remove a parent comment (the scores should also be removed)
Comment::delete(&conn, inserted_comment.id).unwrap(); Comment::delete(&conn, inserted_comment.id).unwrap();
let after_parent_comment_delete = PersonAggregates::read(&conn, inserted_person.id).unwrap(); let after_parent_comment_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
assert_eq!(0, after_parent_comment_delete.comment_count); assert_eq!(0, after_parent_comment_delete.comment_count);
assert_eq!(0, after_parent_comment_delete.comment_score); assert_eq!(0, after_parent_comment_delete.comment_score);
@ -214,24 +227,24 @@ mod tests {
Comment::create(&conn, &child_comment_form).unwrap(); Comment::create(&conn, &child_comment_form).unwrap();
comment_like.comment_id = new_parent_comment.id; comment_like.comment_id = new_parent_comment.id;
CommentLike::like(&conn, &comment_like).unwrap(); CommentLike::like(&conn, &comment_like).unwrap();
let after_comment_add = PersonAggregates::read(&conn, inserted_person.id).unwrap(); let after_comment_add = UserAggregates::read(&conn, inserted_user.id).unwrap();
assert_eq!(2, after_comment_add.comment_count); assert_eq!(2, after_comment_add.comment_count);
assert_eq!(1, after_comment_add.comment_score); assert_eq!(1, after_comment_add.comment_score);
Post::delete(&conn, inserted_post.id).unwrap(); Post::delete(&conn, inserted_post.id).unwrap();
let after_post_delete = PersonAggregates::read(&conn, inserted_person.id).unwrap(); let after_post_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
assert_eq!(0, after_post_delete.comment_score); assert_eq!(0, after_post_delete.comment_score);
assert_eq!(0, after_post_delete.comment_count); assert_eq!(0, after_post_delete.comment_count);
assert_eq!(0, after_post_delete.post_score); assert_eq!(0, after_post_delete.post_score);
assert_eq!(0, after_post_delete.post_count); assert_eq!(0, after_post_delete.post_count);
// This should delete all the associated rows, and fire triggers // This should delete all the associated rows, and fire triggers
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap(); let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(1, person_num_deleted); assert_eq!(1, user_num_deleted);
Person::delete(&conn, another_inserted_person.id).unwrap(); User_::delete(&conn, another_inserted_user.id).unwrap();
// Should be none found // Should be none found
let after_delete = PersonAggregates::read(&conn, inserted_person.id); let after_delete = UserAggregates::read(&conn, inserted_user.id);
assert!(after_delete.is_err()); assert!(after_delete.is_err());
} }
} }

View file

@ -9,33 +9,28 @@ extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate diesel_migrations; extern crate diesel_migrations;
#[cfg(test)]
extern crate serial_test;
use diesel::{result::Error, *}; use diesel::{result::Error, *};
use lemmy_db_schema::{CommunityId, DbUrl, PersonId}; use lemmy_db_schema::Url;
use lemmy_utils::ApiError;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{env, env::VarError}; use std::{env, env::VarError};
use url::Url;
pub mod aggregates; pub mod aggregates;
pub mod source; pub mod source;
pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>; pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
pub trait Crud<Form, IdType> { pub trait Crud<T> {
fn create(conn: &PgConnection, form: &Form) -> Result<Self, Error> fn create(conn: &PgConnection, form: &T) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn read(conn: &PgConnection, id: IdType) -> Result<Self, Error> fn read(conn: &PgConnection, id: i32) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn update(conn: &PgConnection, id: IdType, form: &Form) -> Result<Self, Error> fn update(conn: &PgConnection, id: i32, form: &T) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn delete(_conn: &PgConnection, _id: IdType) -> Result<usize, Error> fn delete(_conn: &PgConnection, _id: i32) -> Result<usize, Error>
where where
Self: Sized, Self: Sized,
{ {
@ -43,85 +38,81 @@ pub trait Crud<Form, IdType> {
} }
} }
pub trait Followable<Form> { pub trait Followable<T> {
fn follow(conn: &PgConnection, form: &Form) -> Result<Self, Error> fn follow(conn: &PgConnection, form: &T) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn follow_accepted( fn follow_accepted(conn: &PgConnection, community_id: i32, user_id: i32) -> Result<Self, Error>
conn: &PgConnection,
community_id: CommunityId,
person_id: PersonId,
) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn unfollow(conn: &PgConnection, form: &Form) -> Result<usize, Error> fn unfollow(conn: &PgConnection, form: &T) -> Result<usize, Error>
where where
Self: Sized; Self: Sized;
fn has_local_followers(conn: &PgConnection, community_id: CommunityId) -> Result<bool, Error>; fn has_local_followers(conn: &PgConnection, community_id: i32) -> Result<bool, Error>;
} }
pub trait Joinable<Form> { pub trait Joinable<T> {
fn join(conn: &PgConnection, form: &Form) -> Result<Self, Error> fn join(conn: &PgConnection, form: &T) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn leave(conn: &PgConnection, form: &Form) -> Result<usize, Error> fn leave(conn: &PgConnection, form: &T) -> Result<usize, Error>
where where
Self: Sized; Self: Sized;
} }
pub trait Likeable<Form, IdType> { pub trait Likeable<T> {
fn like(conn: &PgConnection, form: &Form) -> Result<Self, Error> fn like(conn: &PgConnection, form: &T) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn remove(conn: &PgConnection, person_id: PersonId, item_id: IdType) -> Result<usize, Error> fn remove(conn: &PgConnection, user_id: i32, item_id: i32) -> Result<usize, Error>
where where
Self: Sized; Self: Sized;
} }
pub trait Bannable<Form> { pub trait Bannable<T> {
fn ban(conn: &PgConnection, form: &Form) -> Result<Self, Error> fn ban(conn: &PgConnection, form: &T) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn unban(conn: &PgConnection, form: &Form) -> Result<usize, Error> fn unban(conn: &PgConnection, form: &T) -> Result<usize, Error>
where where
Self: Sized; Self: Sized;
} }
pub trait Saveable<Form> { pub trait Saveable<T> {
fn save(conn: &PgConnection, form: &Form) -> Result<Self, Error> fn save(conn: &PgConnection, form: &T) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn unsave(conn: &PgConnection, form: &Form) -> Result<usize, Error> fn unsave(conn: &PgConnection, form: &T) -> Result<usize, Error>
where where
Self: Sized; Self: Sized;
} }
pub trait Readable<Form> { pub trait Readable<T> {
fn mark_as_read(conn: &PgConnection, form: &Form) -> Result<Self, Error> fn mark_as_read(conn: &PgConnection, form: &T) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn mark_as_unread(conn: &PgConnection, form: &Form) -> Result<usize, Error> fn mark_as_unread(conn: &PgConnection, form: &T) -> Result<usize, Error>
where where
Self: Sized; Self: Sized;
} }
pub trait Reportable<Form> { pub trait Reportable<T> {
fn report(conn: &PgConnection, form: &Form) -> Result<Self, Error> fn report(conn: &PgConnection, form: &T) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn resolve(conn: &PgConnection, report_id: i32, resolver_id: PersonId) -> Result<usize, Error> fn resolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result<usize, Error>
where where
Self: Sized; Self: Sized;
fn unresolve(conn: &PgConnection, report_id: i32, resolver_id: PersonId) -> Result<usize, Error> fn unresolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result<usize, Error>
where where
Self: Sized; Self: Sized;
} }
pub trait ApubObject<Form> { pub trait ApubObject<T> {
fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error> fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn upsert(conn: &PgConnection, user_form: &Form) -> Result<Self, Error> fn upsert(conn: &PgConnection, user_form: &T) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
} }
@ -173,8 +164,6 @@ pub enum SortType {
TopMonth, TopMonth,
TopYear, TopYear,
TopAll, TopAll,
MostComments,
NewComments,
} }
#[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone)] #[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone)]
@ -225,20 +214,6 @@ 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!(); embed_migrations!();
pub fn establish_unpooled_connection() -> PgConnection { pub fn establish_unpooled_connection() -> PgConnection {
@ -251,14 +226,13 @@ pub fn establish_unpooled_connection() -> PgConnection {
}; };
let conn = let conn =
PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url)); PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
embedded_migrations::run(&conn).expect("load migrations"); embedded_migrations::run(&conn).unwrap();
conn conn
} }
lazy_static! { lazy_static! {
static ref EMAIL_REGEX: Regex = static ref EMAIL_REGEX: Regex =
Regex::new(r"^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$") Regex::new(r"^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
.expect("compile email regex");
} }
pub mod functions { pub mod functions {
@ -271,7 +245,7 @@ pub mod functions {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{fuzzy_search, *}; use super::fuzzy_search;
use crate::is_email_regex; use crate::is_email_regex;
#[test] #[test]
@ -285,32 +259,4 @@ mod tests {
assert!(is_email_regex("gush@gmail.com")); assert!(is_email_regex("gush@gmail.com"));
assert!(!is_email_regex("nada_neutho")); 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()
));
}
} }

View file

@ -1,15 +1,14 @@
use crate::Crud; use crate::Crud;
use diesel::{dsl::*, result::Error, sql_types::Text, *}; use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{source::activity::*, DbUrl}; use lemmy_db_schema::source::activity::*;
use log::debug; use log::debug;
use serde::Serialize; use serde::Serialize;
use serde_json::Value;
use std::{ use std::{
fmt::Debug, fmt::Debug,
io::{Error as IoError, ErrorKind}, io::{Error as IoError, ErrorKind},
}; };
impl Crud<ActivityForm, i32> for Activity { impl Crud<ActivityForm> for Activity {
fn read(conn: &PgConnection, activity_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, activity_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::activity::dsl::*; use lemmy_db_schema::schema::activity::dsl::*;
activity.find(activity_id).first::<Self>(conn) activity.find(activity_id).first::<Self>(conn)
@ -41,28 +40,20 @@ impl Crud<ActivityForm, i32> for Activity {
pub trait Activity_ { pub trait Activity_ {
fn insert<T>( fn insert<T>(
conn: &PgConnection, conn: &PgConnection,
ap_id: DbUrl, ap_id: String,
data: &T, data: &T,
local: bool, local: bool,
sensitive: bool, sensitive: bool,
) -> Result<Activity, IoError> ) -> Result<Activity, IoError>
where where
T: Serialize + Debug; 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 { impl Activity_ for Activity {
fn insert<T>( fn insert<T>(
conn: &PgConnection, conn: &PgConnection,
ap_id: DbUrl, ap_id: String,
data: &T, data: &T,
local: bool, local: bool,
sensitive: bool, sensitive: bool,
@ -88,80 +79,62 @@ impl Activity_ for Activity {
} }
} }
fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Activity, Error> { fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Activity, Error> {
use lemmy_db_schema::schema::activity::dsl::*; use lemmy_db_schema::schema::activity::dsl::*;
activity.filter(ap_id.eq(object_id)).first::<Self>(conn) 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use crate::{
use crate::{establish_unpooled_connection, source::activity::Activity_}; establish_unpooled_connection,
source::activity::Activity_,
Crud,
ListingType,
SortType,
};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
activity::{Activity, ActivityForm}, activity::{Activity, ActivityForm},
person::{Person, PersonForm}, user::{UserForm, User_},
}; };
use serde_json::Value; use serde_json::Value;
use serial_test::serial;
use url::Url;
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let creator_form = PersonForm { let creator_form = UserForm {
name: "activity_creator_pm".into(), name: "activity_creator_pm".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_creator = Person::create(&conn, &creator_form).unwrap(); let inserted_creator = User_::create(&conn, &creator_form).unwrap();
let ap_id: DbUrl = Url::parse( let ap_id =
"https://enterprise.lemmy.ml/activities/delete/f1b5d57c-80f8-4e03-a615-688d552e946c", "https://enterprise.lemmy.ml/activities/delete/f1b5d57c-80f8-4e03-a615-688d552e946c";
)
.unwrap()
.into();
let test_json: Value = serde_json::from_str( let test_json: Value = serde_json::from_str(
r#"{ r#"{
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
@ -177,7 +150,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let activity_form = ActivityForm { let activity_form = ActivityForm {
ap_id: ap_id.clone(), ap_id: ap_id.to_string(),
data: test_json.to_owned(), data: test_json.to_owned(),
local: true, local: true,
sensitive: false, sensitive: false,
@ -187,7 +160,7 @@ mod tests {
let inserted_activity = Activity::create(&conn, &activity_form).unwrap(); let inserted_activity = Activity::create(&conn, &activity_form).unwrap();
let expected_activity = Activity { let expected_activity = Activity {
ap_id: Some(ap_id.clone()), ap_id: Some(ap_id.to_string()),
id: inserted_activity.id, id: inserted_activity.id,
data: test_json, data: test_json,
local: true, local: true,
@ -197,8 +170,8 @@ mod tests {
}; };
let read_activity = Activity::read(&conn, inserted_activity.id).unwrap(); 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(); let read_activity_by_apub_id = Activity::read_from_apub_id(&conn, ap_id).unwrap();
Person::delete(&conn, inserted_creator.id).unwrap(); User_::delete(&conn, inserted_creator.id).unwrap();
Activity::delete(&conn, inserted_activity.id).unwrap(); Activity::delete(&conn, inserted_activity.id).unwrap();
assert_eq!(expected_activity, read_activity); assert_eq!(expected_activity, read_activity);

View file

@ -0,0 +1,54 @@
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]);
}
}

View file

@ -10,54 +10,40 @@ use lemmy_db_schema::{
CommentSaved, CommentSaved,
CommentSavedForm, CommentSavedForm,
}, },
CommentId, Url,
DbUrl,
PersonId,
}; };
pub trait Comment_ { pub trait Comment_ {
fn update_ap_id( fn update_ap_id(conn: &PgConnection, comment_id: i32, apub_id: String) -> Result<Comment, Error>;
conn: &PgConnection,
comment_id: CommentId,
apub_id: DbUrl,
) -> Result<Comment, Error>;
fn permadelete_for_creator( fn permadelete_for_creator(
conn: &PgConnection, conn: &PgConnection,
for_creator_id: PersonId, for_creator_id: i32,
) -> Result<Vec<Comment>, Error>; ) -> Result<Vec<Comment>, Error>;
fn update_deleted( fn update_deleted(
conn: &PgConnection, conn: &PgConnection,
comment_id: CommentId, comment_id: i32,
new_deleted: bool, new_deleted: bool,
) -> Result<Comment, Error>; ) -> Result<Comment, Error>;
fn update_removed( fn update_removed(
conn: &PgConnection, conn: &PgConnection,
comment_id: CommentId, comment_id: i32,
new_removed: bool, new_removed: bool,
) -> Result<Comment, Error>; ) -> Result<Comment, Error>;
fn update_removed_for_creator( fn update_removed_for_creator(
conn: &PgConnection, conn: &PgConnection,
for_creator_id: PersonId, for_creator_id: i32,
new_removed: bool, new_removed: bool,
) -> Result<Vec<Comment>, Error>; ) -> Result<Vec<Comment>, Error>;
fn update_read( fn update_read(conn: &PgConnection, comment_id: i32, new_read: bool) -> Result<Comment, Error>;
conn: &PgConnection,
comment_id: CommentId,
new_read: bool,
) -> Result<Comment, Error>;
fn update_content( fn update_content(
conn: &PgConnection, conn: &PgConnection,
comment_id: CommentId, comment_id: i32,
new_content: &str, new_content: &str,
) -> Result<Comment, Error>; ) -> Result<Comment, Error>;
} }
impl Comment_ for Comment { impl Comment_ for Comment {
fn update_ap_id( fn update_ap_id(conn: &PgConnection, comment_id: i32, apub_id: String) -> Result<Self, Error> {
conn: &PgConnection,
comment_id: CommentId,
apub_id: DbUrl,
) -> Result<Self, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
diesel::update(comment.find(comment_id)) diesel::update(comment.find(comment_id))
@ -65,10 +51,7 @@ impl Comment_ for Comment {
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn permadelete_for_creator( fn permadelete_for_creator(conn: &PgConnection, for_creator_id: i32) -> Result<Vec<Self>, Error> {
conn: &PgConnection,
for_creator_id: PersonId,
) -> Result<Vec<Self>, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
diesel::update(comment.filter(creator_id.eq(for_creator_id))) diesel::update(comment.filter(creator_id.eq(for_creator_id)))
.set(( .set((
@ -81,7 +64,7 @@ impl Comment_ for Comment {
fn update_deleted( fn update_deleted(
conn: &PgConnection, conn: &PgConnection,
comment_id: CommentId, comment_id: i32,
new_deleted: bool, new_deleted: bool,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
@ -92,7 +75,7 @@ impl Comment_ for Comment {
fn update_removed( fn update_removed(
conn: &PgConnection, conn: &PgConnection,
comment_id: CommentId, comment_id: i32,
new_removed: bool, new_removed: bool,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
@ -103,7 +86,7 @@ impl Comment_ for Comment {
fn update_removed_for_creator( fn update_removed_for_creator(
conn: &PgConnection, conn: &PgConnection,
for_creator_id: PersonId, for_creator_id: i32,
new_removed: bool, new_removed: bool,
) -> Result<Vec<Self>, Error> { ) -> Result<Vec<Self>, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
@ -112,11 +95,7 @@ impl Comment_ for Comment {
.get_results::<Self>(conn) .get_results::<Self>(conn)
} }
fn update_read( fn update_read(conn: &PgConnection, comment_id: i32, new_read: bool) -> Result<Self, Error> {
conn: &PgConnection,
comment_id: CommentId,
new_read: bool,
) -> Result<Self, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
diesel::update(comment.find(comment_id)) diesel::update(comment.find(comment_id))
.set(read.eq(new_read)) .set(read.eq(new_read))
@ -125,7 +104,7 @@ impl Comment_ for Comment {
fn update_content( fn update_content(
conn: &PgConnection, conn: &PgConnection,
comment_id: CommentId, comment_id: i32,
new_content: &str, new_content: &str,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
@ -135,13 +114,13 @@ impl Comment_ for Comment {
} }
} }
impl Crud<CommentForm, CommentId> for Comment { impl Crud<CommentForm> for Comment {
fn read(conn: &PgConnection, comment_id: CommentId) -> Result<Self, Error> { fn read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
comment.find(comment_id).first::<Self>(conn) comment.find(comment_id).first::<Self>(conn)
} }
fn delete(conn: &PgConnection, comment_id: CommentId) -> Result<usize, Error> { fn delete(conn: &PgConnection, comment_id: i32) -> Result<usize, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
diesel::delete(comment.find(comment_id)).execute(conn) diesel::delete(comment.find(comment_id)).execute(conn)
} }
@ -155,7 +134,7 @@ impl Crud<CommentForm, CommentId> for Comment {
fn update( fn update(
conn: &PgConnection, conn: &PgConnection,
comment_id: CommentId, comment_id: i32,
comment_form: &CommentForm, comment_form: &CommentForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
@ -166,7 +145,7 @@ impl Crud<CommentForm, CommentId> for Comment {
} }
impl ApubObject<CommentForm> for Comment { impl ApubObject<CommentForm> for Comment {
fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error> { fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
comment.filter(ap_id.eq(object_id)).first::<Self>(conn) comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
} }
@ -182,26 +161,22 @@ impl ApubObject<CommentForm> for Comment {
} }
} }
impl Likeable<CommentLikeForm, CommentId> for CommentLike { impl Likeable<CommentLikeForm> for CommentLike {
fn like(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<Self, Error> { fn like(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<Self, Error> {
use lemmy_db_schema::schema::comment_like::dsl::*; use lemmy_db_schema::schema::comment_like::dsl::*;
insert_into(comment_like) insert_into(comment_like)
.values(comment_like_form) .values(comment_like_form)
.on_conflict((comment_id, person_id)) .on_conflict((comment_id, user_id))
.do_update() .do_update()
.set(comment_like_form) .set(comment_like_form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn remove( fn remove(conn: &PgConnection, user_id: i32, comment_id: i32) -> Result<usize, Error> {
conn: &PgConnection,
person_id: PersonId,
comment_id: CommentId,
) -> Result<usize, Error> {
use lemmy_db_schema::schema::comment_like::dsl; use lemmy_db_schema::schema::comment_like::dsl;
diesel::delete( diesel::delete(
dsl::comment_like dsl::comment_like
.filter(dsl::comment_id.eq(comment_id)) .filter(dsl::comment_id.eq(comment_id))
.filter(dsl::person_id.eq(person_id)), .filter(dsl::user_id.eq(user_id)),
) )
.execute(conn) .execute(conn)
} }
@ -212,7 +187,7 @@ impl Saveable<CommentSavedForm> for CommentSaved {
use lemmy_db_schema::schema::comment_saved::dsl::*; use lemmy_db_schema::schema::comment_saved::dsl::*;
insert_into(comment_saved) insert_into(comment_saved)
.values(comment_saved_form) .values(comment_saved_form)
.on_conflict((comment_id, person_id)) .on_conflict((comment_id, user_id))
.do_update() .do_update()
.set(comment_saved_form) .set(comment_saved_form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
@ -222,7 +197,7 @@ impl Saveable<CommentSavedForm> for CommentSaved {
diesel::delete( diesel::delete(
comment_saved comment_saved
.filter(comment_id.eq(comment_saved_form.comment_id)) .filter(comment_id.eq(comment_saved_form.comment_id))
.filter(person_id.eq(comment_saved_form.person_id)), .filter(user_id.eq(comment_saved_form.user_id)),
) )
.execute(conn) .execute(conn)
} }
@ -230,46 +205,53 @@ impl Saveable<CommentSavedForm> for CommentSaved {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{establish_unpooled_connection, Crud, Likeable, Saveable}; use crate::{establish_unpooled_connection, Crud, Likeable, ListingType, Saveable, SortType};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
comment::*, comment::*,
community::{Community, CommunityForm}, community::{Community, CommunityForm},
person::{Person, PersonForm},
post::*, post::*,
user::{UserForm, User_},
}; };
use serial_test::serial;
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let new_person = PersonForm { let new_user = UserForm {
name: "terry".into(), name: "terry".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_person = Person::create(&conn, &new_person).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let new_community = CommunityForm { let new_community = CommunityForm {
name: "test community".to_string(), name: "test community".to_string(),
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
creator_id: inserted_person.id, category_id: 1,
creator_id: inserted_user.id,
removed: None, removed: None,
deleted: None, deleted: None,
updated: None, updated: None,
@ -282,16 +264,13 @@ mod tests {
published: None, published: None,
banner: None, banner: None,
icon: None, icon: None,
inbox_url: None,
shared_inbox_url: None,
followers_url: None,
}; };
let inserted_community = Community::create(&conn, &new_community).unwrap(); let inserted_community = Community::create(&conn, &new_community).unwrap();
let new_post = PostForm { let new_post = PostForm {
name: "A test post".into(), name: "A test post".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
url: None, url: None,
body: None, body: None,
community_id: inserted_community.id, community_id: inserted_community.id,
@ -314,7 +293,7 @@ mod tests {
let comment_form = CommentForm { let comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -331,7 +310,7 @@ mod tests {
let expected_comment = Comment { let expected_comment = Comment {
id: inserted_comment.id, id: inserted_comment.id,
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: false, removed: false,
deleted: false, deleted: false,
@ -345,7 +324,7 @@ mod tests {
let child_comment_form = CommentForm { let child_comment_form = CommentForm {
content: "A child comment".into(), content: "A child comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
parent_id: Some(inserted_comment.id), parent_id: Some(inserted_comment.id),
removed: None, removed: None,
@ -363,7 +342,7 @@ mod tests {
let comment_like_form = CommentLikeForm { let comment_like_form = CommentLikeForm {
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, user_id: inserted_user.id,
score: 1, score: 1,
}; };
@ -373,7 +352,7 @@ mod tests {
id: inserted_comment_like.id, id: inserted_comment_like.id,
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, user_id: inserted_user.id,
published: inserted_comment_like.published, published: inserted_comment_like.published,
score: 1, score: 1,
}; };
@ -381,7 +360,7 @@ mod tests {
// Comment Saved // Comment Saved
let comment_saved_form = CommentSavedForm { let comment_saved_form = CommentSavedForm {
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
person_id: inserted_person.id, user_id: inserted_user.id,
}; };
let inserted_comment_saved = CommentSaved::save(&conn, &comment_saved_form).unwrap(); let inserted_comment_saved = CommentSaved::save(&conn, &comment_saved_form).unwrap();
@ -389,19 +368,19 @@ mod tests {
let expected_comment_saved = CommentSaved { let expected_comment_saved = CommentSaved {
id: inserted_comment_saved.id, id: inserted_comment_saved.id,
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
person_id: inserted_person.id, user_id: inserted_user.id,
published: inserted_comment_saved.published, published: inserted_comment_saved.published,
}; };
let read_comment = Comment::read(&conn, inserted_comment.id).unwrap(); let read_comment = Comment::read(&conn, inserted_comment.id).unwrap();
let updated_comment = Comment::update(&conn, inserted_comment.id, &comment_form).unwrap(); let updated_comment = Comment::update(&conn, inserted_comment.id, &comment_form).unwrap();
let like_removed = CommentLike::remove(&conn, inserted_person.id, inserted_comment.id).unwrap(); let like_removed = CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
let saved_removed = CommentSaved::unsave(&conn, &comment_saved_form).unwrap(); let saved_removed = CommentSaved::unsave(&conn, &comment_saved_form).unwrap();
let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap(); let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
Comment::delete(&conn, inserted_child_comment.id).unwrap(); Comment::delete(&conn, inserted_child_comment.id).unwrap();
Post::delete(&conn, inserted_post.id).unwrap(); Post::delete(&conn, inserted_post.id).unwrap();
Community::delete(&conn, inserted_community.id).unwrap(); Community::delete(&conn, inserted_community.id).unwrap();
Person::delete(&conn, inserted_person.id).unwrap(); User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(expected_comment, read_comment); assert_eq!(expected_comment, read_comment);
assert_eq!(expected_comment, inserted_comment); assert_eq!(expected_comment, inserted_comment);

View file

@ -3,7 +3,6 @@ use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{ use lemmy_db_schema::{
naive_now, naive_now,
source::comment_report::{CommentReport, CommentReportForm}, source::comment_report::{CommentReport, CommentReportForm},
PersonId,
}; };
impl Reportable<CommentReportForm> for CommentReport { impl Reportable<CommentReportForm> for CommentReport {
@ -23,11 +22,7 @@ impl Reportable<CommentReportForm> for CommentReport {
/// * `conn` - the postgres connection /// * `conn` - the postgres connection
/// * `report_id` - the id of the report to resolve /// * `report_id` - the id of the report to resolve
/// * `by_resolver_id` - the id of the user resolving the report /// * `by_resolver_id` - the id of the user resolving the report
fn resolve( fn resolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
conn: &PgConnection,
report_id: i32,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
use lemmy_db_schema::schema::comment_report::dsl::*; use lemmy_db_schema::schema::comment_report::dsl::*;
update(comment_report.find(report_id)) update(comment_report.find(report_id))
.set(( .set((
@ -43,11 +38,7 @@ impl Reportable<CommentReportForm> for CommentReport {
/// * `conn` - the postgres connection /// * `conn` - the postgres connection
/// * `report_id` - the id of the report to unresolve /// * `report_id` - the id of the report to unresolve
/// * `by_resolver_id` - the id of the user unresolving the report /// * `by_resolver_id` - the id of the user unresolving the report
fn unresolve( fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
conn: &PgConnection,
report_id: i32,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
use lemmy_db_schema::schema::comment_report::dsl::*; use lemmy_db_schema::schema::comment_report::dsl::*;
update(comment_report.find(report_id)) update(comment_report.find(report_id))
.set(( .set((

View file

@ -9,12 +9,10 @@ use lemmy_db_schema::{
CommunityForm, CommunityForm,
CommunityModerator, CommunityModerator,
CommunityModeratorForm, CommunityModeratorForm,
CommunityPersonBan, CommunityUserBan,
CommunityPersonBanForm, CommunityUserBanForm,
}, },
CommunityId, Url,
DbUrl,
PersonId,
}; };
mod safe_type { mod safe_type {
@ -26,6 +24,7 @@ mod safe_type {
name, name,
title, title,
description, description,
category_id,
creator_id, creator_id,
removed, removed,
published, published,
@ -46,6 +45,7 @@ mod safe_type {
name, name,
title, title,
description, description,
category_id,
creator_id, creator_id,
removed, removed,
published, published,
@ -61,13 +61,13 @@ mod safe_type {
} }
} }
impl Crud<CommunityForm, CommunityId> for Community { impl Crud<CommunityForm> for Community {
fn read(conn: &PgConnection, community_id: CommunityId) -> Result<Self, Error> { fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::community::dsl::*; use lemmy_db_schema::schema::community::dsl::*;
community.find(community_id).first::<Self>(conn) community.find(community_id).first::<Self>(conn)
} }
fn delete(conn: &PgConnection, community_id: CommunityId) -> Result<usize, Error> { fn delete(conn: &PgConnection, community_id: i32) -> Result<usize, Error> {
use lemmy_db_schema::schema::community::dsl::*; use lemmy_db_schema::schema::community::dsl::*;
diesel::delete(community.find(community_id)).execute(conn) diesel::delete(community.find(community_id)).execute(conn)
} }
@ -81,7 +81,7 @@ impl Crud<CommunityForm, CommunityId> for Community {
fn update( fn update(
conn: &PgConnection, conn: &PgConnection,
community_id: CommunityId, community_id: i32,
new_community: &CommunityForm, new_community: &CommunityForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use lemmy_db_schema::schema::community::dsl::*; use lemmy_db_schema::schema::community::dsl::*;
@ -92,7 +92,7 @@ impl Crud<CommunityForm, CommunityId> for Community {
} }
impl ApubObject<CommunityForm> for Community { impl ApubObject<CommunityForm> for Community {
fn read_from_apub_id(conn: &PgConnection, for_actor_id: &DbUrl) -> Result<Self, Error> { fn read_from_apub_id(conn: &PgConnection, for_actor_id: &Url) -> Result<Self, Error> {
use lemmy_db_schema::schema::community::dsl::*; use lemmy_db_schema::schema::community::dsl::*;
community community
.filter(actor_id.eq(for_actor_id)) .filter(actor_id.eq(for_actor_id))
@ -114,29 +114,25 @@ pub trait Community_ {
fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Community, Error>; fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Community, Error>;
fn update_deleted( fn update_deleted(
conn: &PgConnection, conn: &PgConnection,
community_id: CommunityId, community_id: i32,
new_deleted: bool, new_deleted: bool,
) -> Result<Community, Error>; ) -> Result<Community, Error>;
fn update_removed( fn update_removed(
conn: &PgConnection, conn: &PgConnection,
community_id: CommunityId, community_id: i32,
new_removed: bool, new_removed: bool,
) -> Result<Community, Error>; ) -> Result<Community, Error>;
fn update_removed_for_creator( fn update_removed_for_creator(
conn: &PgConnection, conn: &PgConnection,
for_creator_id: PersonId, for_creator_id: i32,
new_removed: bool, new_removed: bool,
) -> Result<Vec<Community>, Error>; ) -> Result<Vec<Community>, Error>;
fn update_creator( fn update_creator(
conn: &PgConnection, conn: &PgConnection,
community_id: CommunityId, community_id: i32,
new_creator_id: PersonId, new_creator_id: i32,
) -> Result<Community, Error>; ) -> Result<Community, Error>;
fn distinct_federated_communities(conn: &PgConnection) -> Result<Vec<String>, 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 { impl Community_ for Community {
@ -150,7 +146,7 @@ impl Community_ for Community {
fn update_deleted( fn update_deleted(
conn: &PgConnection, conn: &PgConnection,
community_id: CommunityId, community_id: i32,
new_deleted: bool, new_deleted: bool,
) -> Result<Community, Error> { ) -> Result<Community, Error> {
use lemmy_db_schema::schema::community::dsl::*; use lemmy_db_schema::schema::community::dsl::*;
@ -161,7 +157,7 @@ impl Community_ for Community {
fn update_removed( fn update_removed(
conn: &PgConnection, conn: &PgConnection,
community_id: CommunityId, community_id: i32,
new_removed: bool, new_removed: bool,
) -> Result<Community, Error> { ) -> Result<Community, Error> {
use lemmy_db_schema::schema::community::dsl::*; use lemmy_db_schema::schema::community::dsl::*;
@ -172,7 +168,7 @@ impl Community_ for Community {
fn update_removed_for_creator( fn update_removed_for_creator(
conn: &PgConnection, conn: &PgConnection,
for_creator_id: PersonId, for_creator_id: i32,
new_removed: bool, new_removed: bool,
) -> Result<Vec<Community>, Error> { ) -> Result<Vec<Community>, Error> {
use lemmy_db_schema::schema::community::dsl::*; use lemmy_db_schema::schema::community::dsl::*;
@ -183,8 +179,8 @@ impl Community_ for Community {
fn update_creator( fn update_creator(
conn: &PgConnection, conn: &PgConnection,
community_id: CommunityId, community_id: i32,
new_creator_id: PersonId, new_creator_id: i32,
) -> Result<Community, Error> { ) -> Result<Community, Error> {
use lemmy_db_schema::schema::community::dsl::*; use lemmy_db_schema::schema::community::dsl::*;
diesel::update(community.find(community_id)) diesel::update(community.find(community_id))
@ -196,95 +192,79 @@ impl Community_ for Community {
use lemmy_db_schema::schema::community::dsl::*; use lemmy_db_schema::schema::community::dsl::*;
community.select(actor_id).distinct().load::<String>(conn) 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 { impl Joinable<CommunityModeratorForm> for CommunityModerator {
fn join( fn join(
conn: &PgConnection, conn: &PgConnection,
community_moderator_form: &CommunityModeratorForm, community_user_form: &CommunityModeratorForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use lemmy_db_schema::schema::community_moderator::dsl::*; use lemmy_db_schema::schema::community_moderator::dsl::*;
insert_into(community_moderator) insert_into(community_moderator)
.values(community_moderator_form) .values(community_user_form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn leave( fn leave(
conn: &PgConnection, conn: &PgConnection,
community_moderator_form: &CommunityModeratorForm, community_user_form: &CommunityModeratorForm,
) -> Result<usize, Error> { ) -> Result<usize, Error> {
use lemmy_db_schema::schema::community_moderator::dsl::*; use lemmy_db_schema::schema::community_moderator::dsl::*;
diesel::delete( diesel::delete(
community_moderator community_moderator
.filter(community_id.eq(community_moderator_form.community_id)) .filter(community_id.eq(community_user_form.community_id))
.filter(person_id.eq(community_moderator_form.person_id)), .filter(user_id.eq(community_user_form.user_id)),
) )
.execute(conn) .execute(conn)
} }
} }
pub trait CommunityModerator_ { pub trait CommunityModerator_ {
fn delete_for_community( fn delete_for_community(conn: &PgConnection, for_community_id: i32) -> Result<usize, Error>;
fn get_user_moderated_communities(
conn: &PgConnection, conn: &PgConnection,
for_community_id: CommunityId, for_user_id: i32,
) -> Result<usize, Error>; ) -> Result<Vec<i32>, Error>;
fn get_person_moderated_communities(
conn: &PgConnection,
for_person_id: PersonId,
) -> Result<Vec<CommunityId>, Error>;
} }
impl CommunityModerator_ for CommunityModerator { impl CommunityModerator_ for CommunityModerator {
fn delete_for_community( fn delete_for_community(conn: &PgConnection, for_community_id: i32) -> Result<usize, Error> {
conn: &PgConnection,
for_community_id: CommunityId,
) -> Result<usize, Error> {
use lemmy_db_schema::schema::community_moderator::dsl::*; use lemmy_db_schema::schema::community_moderator::dsl::*;
diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn) diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn)
} }
fn get_person_moderated_communities( fn get_user_moderated_communities(
conn: &PgConnection, conn: &PgConnection,
for_person_id: PersonId, for_user_id: i32,
) -> Result<Vec<CommunityId>, Error> { ) -> Result<Vec<i32>, Error> {
use lemmy_db_schema::schema::community_moderator::dsl::*; use lemmy_db_schema::schema::community_moderator::dsl::*;
community_moderator community_moderator
.filter(person_id.eq(for_person_id)) .filter(user_id.eq(for_user_id))
.select(community_id) .select(community_id)
.load::<CommunityId>(conn) .load::<i32>(conn)
} }
} }
impl Bannable<CommunityPersonBanForm> for CommunityPersonBan { impl Bannable<CommunityUserBanForm> for CommunityUserBan {
fn ban( fn ban(
conn: &PgConnection, conn: &PgConnection,
community_person_ban_form: &CommunityPersonBanForm, community_user_ban_form: &CommunityUserBanForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use lemmy_db_schema::schema::community_person_ban::dsl::*; use lemmy_db_schema::schema::community_user_ban::dsl::*;
insert_into(community_person_ban) insert_into(community_user_ban)
.values(community_person_ban_form) .values(community_user_ban_form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn unban( fn unban(
conn: &PgConnection, conn: &PgConnection,
community_person_ban_form: &CommunityPersonBanForm, community_user_ban_form: &CommunityUserBanForm,
) -> Result<usize, Error> { ) -> Result<usize, Error> {
use lemmy_db_schema::schema::community_person_ban::dsl::*; use lemmy_db_schema::schema::community_user_ban::dsl::*;
diesel::delete( diesel::delete(
community_person_ban community_user_ban
.filter(community_id.eq(community_person_ban_form.community_id)) .filter(community_id.eq(community_user_ban_form.community_id))
.filter(person_id.eq(community_person_ban_form.person_id)), .filter(user_id.eq(community_user_ban_form.user_id)),
) )
.execute(conn) .execute(conn)
} }
@ -298,16 +278,12 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
use lemmy_db_schema::schema::community_follower::dsl::*; use lemmy_db_schema::schema::community_follower::dsl::*;
insert_into(community_follower) insert_into(community_follower)
.values(community_follower_form) .values(community_follower_form)
.on_conflict((community_id, person_id)) .on_conflict((community_id, user_id))
.do_update() .do_update()
.set(community_follower_form) .set(community_follower_form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn follow_accepted( fn follow_accepted(conn: &PgConnection, community_id_: i32, user_id_: i32) -> Result<Self, Error>
conn: &PgConnection,
community_id_: CommunityId,
person_id_: PersonId,
) -> Result<Self, Error>
where where
Self: Sized, Self: Sized,
{ {
@ -315,7 +291,7 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
diesel::update( diesel::update(
community_follower community_follower
.filter(community_id.eq(community_id_)) .filter(community_id.eq(community_id_))
.filter(person_id.eq(person_id_)), .filter(user_id.eq(user_id_)),
) )
.set(pending.eq(true)) .set(pending.eq(true))
.get_result::<Self>(conn) .get_result::<Self>(conn)
@ -328,13 +304,13 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
diesel::delete( diesel::delete(
community_follower community_follower
.filter(community_id.eq(&community_follower_form.community_id)) .filter(community_id.eq(&community_follower_form.community_id))
.filter(person_id.eq(&community_follower_form.person_id)), .filter(user_id.eq(&community_follower_form.user_id)),
) )
.execute(conn) .execute(conn)
} }
// TODO: this function name only makes sense if you call it with a remote community. for a local // 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 // community, it will also return true if only remote followers exist
fn has_local_followers(conn: &PgConnection, community_id_: CommunityId) -> Result<bool, Error> { fn has_local_followers(conn: &PgConnection, community_id_: i32) -> Result<bool, Error> {
use lemmy_db_schema::schema::community_follower::dsl::*; use lemmy_db_schema::schema::community_follower::dsl::*;
diesel::select(exists( diesel::select(exists(
community_follower.filter(community_id.eq(community_id_)), community_follower.filter(community_id.eq(community_id_)),
@ -345,41 +321,56 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{establish_unpooled_connection, Bannable, Crud, Followable, Joinable}; use crate::{
use lemmy_db_schema::source::{community::*, person::*}; establish_unpooled_connection,
use serial_test::serial; Bannable,
Crud,
Followable,
Joinable,
ListingType,
SortType,
};
use lemmy_db_schema::source::{community::*, user::*};
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let new_person = PersonForm { let new_user = UserForm {
name: "bobbee".into(), name: "bobbee".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_person = Person::create(&conn, &new_person).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let new_community = CommunityForm { let new_community = CommunityForm {
name: "TIL".into(), name: "TIL".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1,
nsfw: false, nsfw: false,
removed: None, removed: None,
deleted: None, deleted: None,
@ -392,19 +383,17 @@ mod tests {
published: None, published: None,
icon: None, icon: None,
banner: None, banner: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_community = Community::create(&conn, &new_community).unwrap(); let inserted_community = Community::create(&conn, &new_community).unwrap();
let expected_community = Community { let expected_community = Community {
id: inserted_community.id, id: inserted_community.id,
creator_id: inserted_person.id, creator_id: inserted_user.id,
name: "TIL".into(), name: "TIL".into(),
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
category_id: 1,
nsfw: false, nsfw: false,
removed: false, removed: false,
deleted: false, deleted: false,
@ -417,14 +406,11 @@ mod tests {
last_refreshed_at: inserted_community.published, last_refreshed_at: inserted_community.published,
icon: None, icon: None,
banner: 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 { let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: inserted_person.id, user_id: inserted_user.id,
pending: false, pending: false,
}; };
@ -434,56 +420,55 @@ mod tests {
let expected_community_follower = CommunityFollower { let expected_community_follower = CommunityFollower {
id: inserted_community_follower.id, id: inserted_community_follower.id,
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: inserted_person.id, user_id: inserted_user.id,
pending: Some(false), pending: Some(false),
published: inserted_community_follower.published, published: inserted_community_follower.published,
}; };
let community_moderator_form = CommunityModeratorForm { let community_user_form = CommunityModeratorForm {
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: inserted_person.id, user_id: inserted_user.id,
}; };
let inserted_community_moderator = let inserted_community_user = CommunityModerator::join(&conn, &community_user_form).unwrap();
CommunityModerator::join(&conn, &community_moderator_form).unwrap();
let expected_community_moderator = CommunityModerator { let expected_community_user = CommunityModerator {
id: inserted_community_moderator.id, id: inserted_community_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: inserted_person.id, user_id: inserted_user.id,
published: inserted_community_moderator.published, published: inserted_community_user.published,
}; };
let community_person_ban_form = CommunityPersonBanForm { let community_user_ban_form = CommunityUserBanForm {
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: inserted_person.id, user_id: inserted_user.id,
}; };
let inserted_community_person_ban = let inserted_community_user_ban =
CommunityPersonBan::ban(&conn, &community_person_ban_form).unwrap(); CommunityUserBan::ban(&conn, &community_user_ban_form).unwrap();
let expected_community_person_ban = CommunityPersonBan { let expected_community_user_ban = CommunityUserBan {
id: inserted_community_person_ban.id, id: inserted_community_user_ban.id,
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: inserted_person.id, user_id: inserted_user.id,
published: inserted_community_person_ban.published, published: inserted_community_user_ban.published,
}; };
let read_community = Community::read(&conn, inserted_community.id).unwrap(); let read_community = Community::read(&conn, inserted_community.id).unwrap();
let updated_community = let updated_community =
Community::update(&conn, inserted_community.id, &new_community).unwrap(); Community::update(&conn, inserted_community.id, &new_community).unwrap();
let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap(); let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap();
let left_community = CommunityModerator::leave(&conn, &community_moderator_form).unwrap(); let left_community = CommunityModerator::leave(&conn, &community_user_form).unwrap();
let unban = CommunityPersonBan::unban(&conn, &community_person_ban_form).unwrap(); let unban = CommunityUserBan::unban(&conn, &community_user_ban_form).unwrap();
let num_deleted = Community::delete(&conn, inserted_community.id).unwrap(); let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
Person::delete(&conn, inserted_person.id).unwrap(); User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(expected_community, read_community); assert_eq!(expected_community, read_community);
assert_eq!(expected_community, inserted_community); assert_eq!(expected_community, inserted_community);
assert_eq!(expected_community, updated_community); assert_eq!(expected_community, updated_community);
assert_eq!(expected_community_follower, inserted_community_follower); assert_eq!(expected_community_follower, inserted_community_follower);
assert_eq!(expected_community_moderator, inserted_community_moderator); assert_eq!(expected_community_user, inserted_community_user);
assert_eq!(expected_community_person_ban, inserted_community_person_ban); assert_eq!(expected_community_user_ban, inserted_community_user_ban);
assert_eq!(1, ignored_community); assert_eq!(1, ignored_community);
assert_eq!(1, left_community); assert_eq!(1, left_community);
assert_eq!(1, unban); assert_eq!(1, unban);

View file

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

View file

@ -1,13 +1,13 @@
pub mod activity; pub mod activity;
pub mod category;
pub mod comment; pub mod comment;
pub mod comment_report; pub mod comment_report;
pub mod community; pub mod community;
pub mod local_user;
pub mod moderator; pub mod moderator;
pub mod password_reset_request; pub mod password_reset_request;
pub mod person;
pub mod person_mention;
pub mod post; pub mod post;
pub mod post_report; pub mod post_report;
pub mod private_message; pub mod private_message;
pub mod site; pub mod site;
pub mod user;
pub mod user_mention;

View file

@ -2,7 +2,7 @@ use crate::Crud;
use diesel::{dsl::*, result::Error, *}; use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::source::moderator::*; use lemmy_db_schema::source::moderator::*;
impl Crud<ModRemovePostForm, i32> for ModRemovePost { impl Crud<ModRemovePostForm> for ModRemovePost {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::mod_remove_post::dsl::*; use lemmy_db_schema::schema::mod_remove_post::dsl::*;
mod_remove_post.find(from_id).first::<Self>(conn) mod_remove_post.find(from_id).first::<Self>(conn)
@ -23,7 +23,7 @@ impl Crud<ModRemovePostForm, i32> for ModRemovePost {
} }
} }
impl Crud<ModLockPostForm, i32> for ModLockPost { impl Crud<ModLockPostForm> for ModLockPost {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::mod_lock_post::dsl::*; use lemmy_db_schema::schema::mod_lock_post::dsl::*;
mod_lock_post.find(from_id).first::<Self>(conn) mod_lock_post.find(from_id).first::<Self>(conn)
@ -44,7 +44,7 @@ impl Crud<ModLockPostForm, i32> for ModLockPost {
} }
} }
impl Crud<ModStickyPostForm, i32> for ModStickyPost { impl Crud<ModStickyPostForm> for ModStickyPost {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::mod_sticky_post::dsl::*; use lemmy_db_schema::schema::mod_sticky_post::dsl::*;
mod_sticky_post.find(from_id).first::<Self>(conn) mod_sticky_post.find(from_id).first::<Self>(conn)
@ -65,7 +65,7 @@ impl Crud<ModStickyPostForm, i32> for ModStickyPost {
} }
} }
impl Crud<ModRemoveCommentForm, i32> for ModRemoveComment { impl Crud<ModRemoveCommentForm> for ModRemoveComment {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::mod_remove_comment::dsl::*; use lemmy_db_schema::schema::mod_remove_comment::dsl::*;
mod_remove_comment.find(from_id).first::<Self>(conn) mod_remove_comment.find(from_id).first::<Self>(conn)
@ -86,7 +86,7 @@ impl Crud<ModRemoveCommentForm, i32> for ModRemoveComment {
} }
} }
impl Crud<ModRemoveCommunityForm, i32> for ModRemoveCommunity { impl Crud<ModRemoveCommunityForm> for ModRemoveCommunity {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::mod_remove_community::dsl::*; use lemmy_db_schema::schema::mod_remove_community::dsl::*;
mod_remove_community.find(from_id).first::<Self>(conn) mod_remove_community.find(from_id).first::<Self>(conn)
@ -111,7 +111,7 @@ impl Crud<ModRemoveCommunityForm, i32> for ModRemoveCommunity {
} }
} }
impl Crud<ModBanFromCommunityForm, i32> for ModBanFromCommunity { impl Crud<ModBanFromCommunityForm> for ModBanFromCommunity {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::mod_ban_from_community::dsl::*; use lemmy_db_schema::schema::mod_ban_from_community::dsl::*;
mod_ban_from_community.find(from_id).first::<Self>(conn) mod_ban_from_community.find(from_id).first::<Self>(conn)
@ -136,7 +136,7 @@ impl Crud<ModBanFromCommunityForm, i32> for ModBanFromCommunity {
} }
} }
impl Crud<ModBanForm, i32> for ModBan { impl Crud<ModBanForm> for ModBan {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::mod_ban::dsl::*; use lemmy_db_schema::schema::mod_ban::dsl::*;
mod_ban.find(from_id).first::<Self>(conn) mod_ban.find(from_id).first::<Self>(conn)
@ -155,7 +155,7 @@ impl Crud<ModBanForm, i32> for ModBan {
} }
} }
impl Crud<ModAddCommunityForm, i32> for ModAddCommunity { impl Crud<ModAddCommunityForm> for ModAddCommunity {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::mod_add_community::dsl::*; use lemmy_db_schema::schema::mod_add_community::dsl::*;
mod_add_community.find(from_id).first::<Self>(conn) mod_add_community.find(from_id).first::<Self>(conn)
@ -176,7 +176,7 @@ impl Crud<ModAddCommunityForm, i32> for ModAddCommunity {
} }
} }
impl Crud<ModAddForm, i32> for ModAdd { impl Crud<ModAddForm> for ModAdd {
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::mod_add::dsl::*; use lemmy_db_schema::schema::mod_add::dsl::*;
mod_add.find(from_id).first::<Self>(conn) mod_add.find(from_id).first::<Self>(conn)
@ -197,63 +197,78 @@ impl Crud<ModAddForm, i32> for ModAdd {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{establish_unpooled_connection, Crud}; use crate::{establish_unpooled_connection, Crud, ListingType, SortType};
use lemmy_db_schema::source::{comment::*, community::*, moderator::*, person::*, post::*}; use lemmy_db_schema::source::{comment::*, community::*, moderator::*, post::*, user::*};
use serial_test::serial;
// use Crud; // use Crud;
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let new_mod = PersonForm { let new_mod = UserForm {
name: "the mod".into(), name: "the mod".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_mod = Person::create(&conn, &new_mod).unwrap(); let inserted_mod = User_::create(&conn, &new_mod).unwrap();
let new_person = PersonForm { let new_user = UserForm {
name: "jim2".into(), name: "jim2".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_person = Person::create(&conn, &new_person).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let new_community = CommunityForm { let new_community = CommunityForm {
name: "mod_community".to_string(), name: "mod_community".to_string(),
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
creator_id: inserted_person.id, category_id: 1,
creator_id: inserted_user.id,
removed: None, removed: None,
deleted: None, deleted: None,
updated: None, updated: None,
@ -266,9 +281,6 @@ mod tests {
published: None, published: None,
icon: None, icon: None,
banner: None, banner: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_community = Community::create(&conn, &new_community).unwrap(); let inserted_community = Community::create(&conn, &new_community).unwrap();
@ -277,7 +289,7 @@ mod tests {
name: "A test post thweep".into(), name: "A test post thweep".into(),
url: None, url: None,
body: None, body: None,
creator_id: inserted_person.id, creator_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -298,7 +310,7 @@ mod tests {
let comment_form = CommentForm { let comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -316,7 +328,7 @@ mod tests {
// remove post // remove post
let mod_remove_post_form = ModRemovePostForm { let mod_remove_post_form = ModRemovePostForm {
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
post_id: inserted_post.id, post_id: inserted_post.id,
reason: None, reason: None,
removed: None, removed: None,
@ -326,7 +338,7 @@ mod tests {
let expected_mod_remove_post = ModRemovePost { let expected_mod_remove_post = ModRemovePost {
id: inserted_mod_remove_post.id, id: inserted_mod_remove_post.id,
post_id: inserted_post.id, post_id: inserted_post.id,
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
reason: None, reason: None,
removed: Some(true), removed: Some(true),
when_: inserted_mod_remove_post.when_, when_: inserted_mod_remove_post.when_,
@ -335,7 +347,7 @@ mod tests {
// lock post // lock post
let mod_lock_post_form = ModLockPostForm { let mod_lock_post_form = ModLockPostForm {
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
post_id: inserted_post.id, post_id: inserted_post.id,
locked: None, locked: None,
}; };
@ -344,7 +356,7 @@ mod tests {
let expected_mod_lock_post = ModLockPost { let expected_mod_lock_post = ModLockPost {
id: inserted_mod_lock_post.id, id: inserted_mod_lock_post.id,
post_id: inserted_post.id, post_id: inserted_post.id,
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
locked: Some(true), locked: Some(true),
when_: inserted_mod_lock_post.when_, when_: inserted_mod_lock_post.when_,
}; };
@ -352,7 +364,7 @@ mod tests {
// sticky post // sticky post
let mod_sticky_post_form = ModStickyPostForm { let mod_sticky_post_form = ModStickyPostForm {
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
post_id: inserted_post.id, post_id: inserted_post.id,
stickied: None, stickied: None,
}; };
@ -361,7 +373,7 @@ mod tests {
let expected_mod_sticky_post = ModStickyPost { let expected_mod_sticky_post = ModStickyPost {
id: inserted_mod_sticky_post.id, id: inserted_mod_sticky_post.id,
post_id: inserted_post.id, post_id: inserted_post.id,
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
stickied: Some(true), stickied: Some(true),
when_: inserted_mod_sticky_post.when_, when_: inserted_mod_sticky_post.when_,
}; };
@ -369,7 +381,7 @@ mod tests {
// comment // comment
let mod_remove_comment_form = ModRemoveCommentForm { let mod_remove_comment_form = ModRemoveCommentForm {
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
reason: None, reason: None,
removed: None, removed: None,
@ -381,7 +393,7 @@ mod tests {
let expected_mod_remove_comment = ModRemoveComment { let expected_mod_remove_comment = ModRemoveComment {
id: inserted_mod_remove_comment.id, id: inserted_mod_remove_comment.id,
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
reason: None, reason: None,
removed: Some(true), removed: Some(true),
when_: inserted_mod_remove_comment.when_, when_: inserted_mod_remove_comment.when_,
@ -390,7 +402,7 @@ mod tests {
// community // community
let mod_remove_community_form = ModRemoveCommunityForm { let mod_remove_community_form = ModRemoveCommunityForm {
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
community_id: inserted_community.id, community_id: inserted_community.id,
reason: None, reason: None,
removed: None, removed: None,
@ -403,7 +415,7 @@ mod tests {
let expected_mod_remove_community = ModRemoveCommunity { let expected_mod_remove_community = ModRemoveCommunity {
id: inserted_mod_remove_community.id, id: inserted_mod_remove_community.id,
community_id: inserted_community.id, community_id: inserted_community.id,
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
reason: None, reason: None,
removed: Some(true), removed: Some(true),
expires: None, expires: None,
@ -413,8 +425,8 @@ mod tests {
// ban from community // ban from community
let mod_ban_from_community_form = ModBanFromCommunityForm { let mod_ban_from_community_form = ModBanFromCommunityForm {
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
other_person_id: inserted_person.id, other_user_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
reason: None, reason: None,
banned: None, banned: None,
@ -427,8 +439,8 @@ mod tests {
let expected_mod_ban_from_community = ModBanFromCommunity { let expected_mod_ban_from_community = ModBanFromCommunity {
id: inserted_mod_ban_from_community.id, id: inserted_mod_ban_from_community.id,
community_id: inserted_community.id, community_id: inserted_community.id,
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
other_person_id: inserted_person.id, other_user_id: inserted_user.id,
reason: None, reason: None,
banned: Some(true), banned: Some(true),
expires: None, expires: None,
@ -438,8 +450,8 @@ mod tests {
// ban // ban
let mod_ban_form = ModBanForm { let mod_ban_form = ModBanForm {
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
other_person_id: inserted_person.id, other_user_id: inserted_user.id,
reason: None, reason: None,
banned: None, banned: None,
expires: None, expires: None,
@ -448,8 +460,8 @@ mod tests {
let read_mod_ban = ModBan::read(&conn, inserted_mod_ban.id).unwrap(); let read_mod_ban = ModBan::read(&conn, inserted_mod_ban.id).unwrap();
let expected_mod_ban = ModBan { let expected_mod_ban = ModBan {
id: inserted_mod_ban.id, id: inserted_mod_ban.id,
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
other_person_id: inserted_person.id, other_user_id: inserted_user.id,
reason: None, reason: None,
banned: Some(true), banned: Some(true),
expires: None, expires: None,
@ -459,8 +471,8 @@ mod tests {
// mod add community // mod add community
let mod_add_community_form = ModAddCommunityForm { let mod_add_community_form = ModAddCommunityForm {
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
other_person_id: inserted_person.id, other_user_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None, removed: None,
}; };
@ -471,8 +483,8 @@ mod tests {
let expected_mod_add_community = ModAddCommunity { let expected_mod_add_community = ModAddCommunity {
id: inserted_mod_add_community.id, id: inserted_mod_add_community.id,
community_id: inserted_community.id, community_id: inserted_community.id,
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
other_person_id: inserted_person.id, other_user_id: inserted_user.id,
removed: Some(false), removed: Some(false),
when_: inserted_mod_add_community.when_, when_: inserted_mod_add_community.when_,
}; };
@ -480,16 +492,16 @@ mod tests {
// mod add // mod add
let mod_add_form = ModAddForm { let mod_add_form = ModAddForm {
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
other_person_id: inserted_person.id, other_user_id: inserted_user.id,
removed: None, removed: None,
}; };
let inserted_mod_add = ModAdd::create(&conn, &mod_add_form).unwrap(); let inserted_mod_add = ModAdd::create(&conn, &mod_add_form).unwrap();
let read_mod_add = ModAdd::read(&conn, inserted_mod_add.id).unwrap(); let read_mod_add = ModAdd::read(&conn, inserted_mod_add.id).unwrap();
let expected_mod_add = ModAdd { let expected_mod_add = ModAdd {
id: inserted_mod_add.id, id: inserted_mod_add.id,
mod_person_id: inserted_mod.id, mod_user_id: inserted_mod.id,
other_person_id: inserted_person.id, other_user_id: inserted_user.id,
removed: Some(false), removed: Some(false),
when_: inserted_mod_add.when_, when_: inserted_mod_add.when_,
}; };
@ -497,8 +509,8 @@ mod tests {
Comment::delete(&conn, inserted_comment.id).unwrap(); Comment::delete(&conn, inserted_comment.id).unwrap();
Post::delete(&conn, inserted_post.id).unwrap(); Post::delete(&conn, inserted_post.id).unwrap();
Community::delete(&conn, inserted_community.id).unwrap(); Community::delete(&conn, inserted_community.id).unwrap();
Person::delete(&conn, inserted_person.id).unwrap(); User_::delete(&conn, inserted_user.id).unwrap();
Person::delete(&conn, inserted_mod.id).unwrap(); User_::delete(&conn, inserted_mod.id).unwrap();
assert_eq!(expected_mod_remove_post, read_mod_remove_post); assert_eq!(expected_mod_remove_post, read_mod_remove_post);
assert_eq!(expected_mod_lock_post, read_mod_lock_post); assert_eq!(expected_mod_lock_post, read_mod_lock_post);

View file

@ -1,13 +1,9 @@
use crate::Crud; use crate::Crud;
use diesel::{dsl::*, result::Error, PgConnection, *}; use diesel::{dsl::*, result::Error, PgConnection, *};
use lemmy_db_schema::{ use lemmy_db_schema::{schema::password_reset_request::dsl::*, source::password_reset_request::*};
schema::password_reset_request::dsl::*,
source::password_reset_request::*,
LocalUserId,
};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
impl Crud<PasswordResetRequestForm, i32> for PasswordResetRequest { impl Crud<PasswordResetRequestForm> for PasswordResetRequest {
fn read(conn: &PgConnection, password_reset_request_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, password_reset_request_id: i32) -> Result<Self, Error> {
password_reset_request password_reset_request
.find(password_reset_request_id) .find(password_reset_request_id)
@ -32,7 +28,7 @@ impl Crud<PasswordResetRequestForm, i32> for PasswordResetRequest {
pub trait PasswordResetRequest_ { pub trait PasswordResetRequest_ {
fn create_token( fn create_token(
conn: &PgConnection, conn: &PgConnection,
from_local_user_id: LocalUserId, from_user_id: i32,
token: &str, token: &str,
) -> Result<PasswordResetRequest, Error>; ) -> Result<PasswordResetRequest, Error>;
fn read_from_token(conn: &PgConnection, token: &str) -> Result<PasswordResetRequest, Error>; fn read_from_token(conn: &PgConnection, token: &str) -> Result<PasswordResetRequest, Error>;
@ -41,7 +37,7 @@ pub trait PasswordResetRequest_ {
impl PasswordResetRequest_ for PasswordResetRequest { impl PasswordResetRequest_ for PasswordResetRequest {
fn create_token( fn create_token(
conn: &PgConnection, conn: &PgConnection,
from_local_user_id: LocalUserId, from_user_id: i32,
token: &str, token: &str,
) -> Result<PasswordResetRequest, Error> { ) -> Result<PasswordResetRequest, Error> {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
@ -49,7 +45,7 @@ impl PasswordResetRequest_ for PasswordResetRequest {
let token_hash: String = bytes_to_hex(hasher.finalize().to_vec()); let token_hash: String = bytes_to_hex(hasher.finalize().to_vec());
let form = PasswordResetRequestForm { let form = PasswordResetRequestForm {
local_user_id: from_local_user_id, user_id: from_user_id,
token_encrypted: token_hash, token_encrypted: token_hash,
}; };
@ -80,72 +76,59 @@ mod tests {
establish_unpooled_connection, establish_unpooled_connection,
source::password_reset_request::PasswordResetRequest_, source::password_reset_request::PasswordResetRequest_,
Crud, Crud,
ListingType,
SortType,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{password_reset_request::PasswordResetRequest, user::*};
local_user::{LocalUser, LocalUserForm},
password_reset_request::PasswordResetRequest,
person::*,
};
use serial_test::serial;
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let new_person = PersonForm { let new_user = UserForm {
name: "thommy prw".into(), name: "thommy prw".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_person = Person::create(&conn, &new_person).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let new_local_user = LocalUserForm {
person_id: inserted_person.id,
password_encrypted: "pass".to_string(),
email: None,
matrix_user_id: None,
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, &new_local_user).unwrap();
let token = "nope"; let token = "nope";
let token_encrypted_ = "ca3704aa0b06f5954c79ee837faa152d84d6b2d42838f0637a15eda8337dbdce"; let token_encrypted_ = "ca3704aa0b06f5954c79ee837faa152d84d6b2d42838f0637a15eda8337dbdce";
let inserted_password_reset_request = let inserted_password_reset_request =
PasswordResetRequest::create_token(&conn, inserted_local_user.id, token).unwrap(); PasswordResetRequest::create_token(&conn, inserted_user.id, token).unwrap();
let expected_password_reset_request = PasswordResetRequest { let expected_password_reset_request = PasswordResetRequest {
id: inserted_password_reset_request.id, id: inserted_password_reset_request.id,
local_user_id: inserted_local_user.id, user_id: inserted_user.id,
token_encrypted: token_encrypted_.to_string(), token_encrypted: token_encrypted_.to_string(),
published: inserted_password_reset_request.published, published: inserted_password_reset_request.published,
}; };
let read_password_reset_request = PasswordResetRequest::read_from_token(&conn, token).unwrap(); let read_password_reset_request = PasswordResetRequest::read_from_token(&conn, token).unwrap();
let num_deleted = Person::delete(&conn, inserted_person.id).unwrap(); let num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(expected_password_reset_request, read_password_reset_request); assert_eq!(expected_password_reset_request, read_password_reset_request);
assert_eq!( assert_eq!(

View file

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

View file

@ -12,19 +12,16 @@ use lemmy_db_schema::{
PostSaved, PostSaved,
PostSavedForm, PostSavedForm,
}, },
CommunityId, Url,
DbUrl,
PersonId,
PostId,
}; };
impl Crud<PostForm, PostId> for Post { impl Crud<PostForm> for Post {
fn read(conn: &PgConnection, post_id: PostId) -> Result<Self, Error> { fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
post.find(post_id).first::<Self>(conn) post.find(post_id).first::<Self>(conn)
} }
fn delete(conn: &PgConnection, post_id: PostId) -> Result<usize, Error> { fn delete(conn: &PgConnection, post_id: i32) -> Result<usize, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
diesel::delete(post.find(post_id)).execute(conn) diesel::delete(post.find(post_id)).execute(conn)
} }
@ -34,7 +31,7 @@ impl Crud<PostForm, PostId> for Post {
insert_into(post).values(new_post).get_result::<Self>(conn) insert_into(post).values(new_post).get_result::<Self>(conn)
} }
fn update(conn: &PgConnection, post_id: PostId, new_post: &PostForm) -> Result<Self, Error> { fn update(conn: &PgConnection, post_id: i32, new_post: &PostForm) -> Result<Self, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
diesel::update(post.find(post_id)) diesel::update(post.find(post_id))
.set(new_post) .set(new_post)
@ -44,39 +41,24 @@ impl Crud<PostForm, PostId> for Post {
pub trait Post_ { pub trait Post_ {
//fn read(conn: &PgConnection, post_id: i32) -> Result<Post, Error>; //fn read(conn: &PgConnection, post_id: i32) -> Result<Post, Error>;
fn list_for_community( fn list_for_community(conn: &PgConnection, the_community_id: i32) -> Result<Vec<Post>, Error>;
conn: &PgConnection, fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: String) -> Result<Post, Error>;
the_community_id: CommunityId, fn permadelete_for_creator(conn: &PgConnection, for_creator_id: i32) -> Result<Vec<Post>, Error>;
) -> Result<Vec<Post>, Error>; fn update_deleted(conn: &PgConnection, post_id: i32, new_deleted: bool) -> Result<Post, Error>;
fn update_ap_id(conn: &PgConnection, post_id: PostId, apub_id: DbUrl) -> Result<Post, Error>; fn update_removed(conn: &PgConnection, post_id: i32, new_removed: bool) -> Result<Post, Error>;
fn permadelete_for_creator(
conn: &PgConnection,
for_creator_id: PersonId,
) -> Result<Vec<Post>, Error>;
fn update_deleted(conn: &PgConnection, post_id: PostId, new_deleted: bool)
-> Result<Post, Error>;
fn update_removed(conn: &PgConnection, post_id: PostId, new_removed: bool)
-> Result<Post, Error>;
fn update_removed_for_creator( fn update_removed_for_creator(
conn: &PgConnection, conn: &PgConnection,
for_creator_id: PersonId, for_creator_id: i32,
for_community_id: Option<CommunityId>, for_community_id: Option<i32>,
new_removed: bool, new_removed: bool,
) -> Result<Vec<Post>, Error>; ) -> Result<Vec<Post>, Error>;
fn update_locked(conn: &PgConnection, post_id: PostId, new_locked: bool) -> Result<Post, Error>; fn update_locked(conn: &PgConnection, post_id: i32, new_locked: bool) -> Result<Post, Error>;
fn update_stickied( fn update_stickied(conn: &PgConnection, post_id: i32, new_stickied: bool) -> Result<Post, Error>;
conn: &PgConnection, fn is_post_creator(user_id: i32, post_creator_id: i32) -> bool;
post_id: PostId,
new_stickied: bool,
) -> Result<Post, Error>;
fn is_post_creator(person_id: PersonId, post_creator_id: PersonId) -> bool;
} }
impl Post_ for Post { impl Post_ for Post {
fn list_for_community( fn list_for_community(conn: &PgConnection, the_community_id: i32) -> Result<Vec<Self>, Error> {
conn: &PgConnection,
the_community_id: CommunityId,
) -> Result<Vec<Self>, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
post post
.filter(community_id.eq(the_community_id)) .filter(community_id.eq(the_community_id))
@ -86,7 +68,7 @@ impl Post_ for Post {
.load::<Self>(conn) .load::<Self>(conn)
} }
fn update_ap_id(conn: &PgConnection, post_id: PostId, apub_id: DbUrl) -> Result<Self, Error> { fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: String) -> Result<Self, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
diesel::update(post.find(post_id)) diesel::update(post.find(post_id))
@ -94,10 +76,7 @@ impl Post_ for Post {
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn permadelete_for_creator( fn permadelete_for_creator(conn: &PgConnection, for_creator_id: i32) -> Result<Vec<Self>, Error> {
conn: &PgConnection,
for_creator_id: PersonId,
) -> Result<Vec<Self>, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
let perma_deleted = "*Permananently Deleted*"; let perma_deleted = "*Permananently Deleted*";
@ -114,22 +93,14 @@ impl Post_ for Post {
.get_results::<Self>(conn) .get_results::<Self>(conn)
} }
fn update_deleted( fn update_deleted(conn: &PgConnection, post_id: i32, new_deleted: bool) -> Result<Self, Error> {
conn: &PgConnection,
post_id: PostId,
new_deleted: bool,
) -> Result<Self, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
diesel::update(post.find(post_id)) diesel::update(post.find(post_id))
.set((deleted.eq(new_deleted), updated.eq(naive_now()))) .set((deleted.eq(new_deleted), updated.eq(naive_now())))
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn update_removed( fn update_removed(conn: &PgConnection, post_id: i32, new_removed: bool) -> Result<Self, Error> {
conn: &PgConnection,
post_id: PostId,
new_removed: bool,
) -> Result<Self, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
diesel::update(post.find(post_id)) diesel::update(post.find(post_id))
.set((removed.eq(new_removed), updated.eq(naive_now()))) .set((removed.eq(new_removed), updated.eq(naive_now())))
@ -138,8 +109,8 @@ impl Post_ for Post {
fn update_removed_for_creator( fn update_removed_for_creator(
conn: &PgConnection, conn: &PgConnection,
for_creator_id: PersonId, for_creator_id: i32,
for_community_id: Option<CommunityId>, for_community_id: Option<i32>,
new_removed: bool, new_removed: bool,
) -> Result<Vec<Self>, Error> { ) -> Result<Vec<Self>, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
@ -156,31 +127,27 @@ impl Post_ for Post {
.get_results::<Self>(conn) .get_results::<Self>(conn)
} }
fn update_locked(conn: &PgConnection, post_id: PostId, new_locked: bool) -> Result<Self, Error> { fn update_locked(conn: &PgConnection, post_id: i32, new_locked: bool) -> Result<Self, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
diesel::update(post.find(post_id)) diesel::update(post.find(post_id))
.set(locked.eq(new_locked)) .set(locked.eq(new_locked))
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn update_stickied( fn update_stickied(conn: &PgConnection, post_id: i32, new_stickied: bool) -> Result<Self, Error> {
conn: &PgConnection,
post_id: PostId,
new_stickied: bool,
) -> Result<Self, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
diesel::update(post.find(post_id)) diesel::update(post.find(post_id))
.set(stickied.eq(new_stickied)) .set(stickied.eq(new_stickied))
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn is_post_creator(person_id: PersonId, post_creator_id: PersonId) -> bool { fn is_post_creator(user_id: i32, post_creator_id: i32) -> bool {
person_id == post_creator_id user_id == post_creator_id
} }
} }
impl ApubObject<PostForm> for Post { impl ApubObject<PostForm> for Post {
fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error> { fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
post.filter(ap_id.eq(object_id)).first::<Self>(conn) post.filter(ap_id.eq(object_id)).first::<Self>(conn)
} }
@ -196,22 +163,22 @@ impl ApubObject<PostForm> for Post {
} }
} }
impl Likeable<PostLikeForm, PostId> for PostLike { impl Likeable<PostLikeForm> for PostLike {
fn like(conn: &PgConnection, post_like_form: &PostLikeForm) -> Result<Self, Error> { fn like(conn: &PgConnection, post_like_form: &PostLikeForm) -> Result<Self, Error> {
use lemmy_db_schema::schema::post_like::dsl::*; use lemmy_db_schema::schema::post_like::dsl::*;
insert_into(post_like) insert_into(post_like)
.values(post_like_form) .values(post_like_form)
.on_conflict((post_id, person_id)) .on_conflict((post_id, user_id))
.do_update() .do_update()
.set(post_like_form) .set(post_like_form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn remove(conn: &PgConnection, person_id: PersonId, post_id: PostId) -> Result<usize, Error> { fn remove(conn: &PgConnection, user_id: i32, post_id: i32) -> Result<usize, Error> {
use lemmy_db_schema::schema::post_like::dsl; use lemmy_db_schema::schema::post_like::dsl;
diesel::delete( diesel::delete(
dsl::post_like dsl::post_like
.filter(dsl::post_id.eq(post_id)) .filter(dsl::post_id.eq(post_id))
.filter(dsl::person_id.eq(person_id)), .filter(dsl::user_id.eq(user_id)),
) )
.execute(conn) .execute(conn)
} }
@ -222,7 +189,7 @@ impl Saveable<PostSavedForm> for PostSaved {
use lemmy_db_schema::schema::post_saved::dsl::*; use lemmy_db_schema::schema::post_saved::dsl::*;
insert_into(post_saved) insert_into(post_saved)
.values(post_saved_form) .values(post_saved_form)
.on_conflict((post_id, person_id)) .on_conflict((post_id, user_id))
.do_update() .do_update()
.set(post_saved_form) .set(post_saved_form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
@ -232,7 +199,7 @@ impl Saveable<PostSavedForm> for PostSaved {
diesel::delete( diesel::delete(
post_saved post_saved
.filter(post_id.eq(post_saved_form.post_id)) .filter(post_id.eq(post_saved_form.post_id))
.filter(person_id.eq(post_saved_form.person_id)), .filter(user_id.eq(post_saved_form.user_id)),
) )
.execute(conn) .execute(conn)
} }
@ -251,7 +218,7 @@ impl Readable<PostReadForm> for PostRead {
diesel::delete( diesel::delete(
post_read post_read
.filter(post_id.eq(post_read_form.post_id)) .filter(post_id.eq(post_read_form.post_id))
.filter(person_id.eq(post_read_form.person_id)), .filter(user_id.eq(post_read_form.user_id)),
) )
.execute(conn) .execute(conn)
} }
@ -259,44 +226,51 @@ impl Readable<PostReadForm> for PostRead {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{establish_unpooled_connection, source::post::*}; use crate::{establish_unpooled_connection, source::post::*, ListingType, SortType};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
community::{Community, CommunityForm}, community::{Community, CommunityForm},
person::*, user::*,
}; };
use serial_test::serial;
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let new_person = PersonForm { let new_user = UserForm {
name: "jim".into(), name: "jim".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_person = Person::create(&conn, &new_person).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let new_community = CommunityForm { let new_community = CommunityForm {
name: "test community_3".to_string(), name: "test community_3".to_string(),
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
creator_id: inserted_person.id, category_id: 1,
creator_id: inserted_user.id,
removed: None, removed: None,
deleted: None, deleted: None,
updated: None, updated: None,
@ -309,9 +283,6 @@ mod tests {
published: None, published: None,
icon: None, icon: None,
banner: None, banner: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_community = Community::create(&conn, &new_community).unwrap(); let inserted_community = Community::create(&conn, &new_community).unwrap();
@ -320,7 +291,7 @@ mod tests {
name: "A test post".into(), name: "A test post".into(),
url: None, url: None,
body: None, body: None,
creator_id: inserted_person.id, creator_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -344,7 +315,7 @@ mod tests {
name: "A test post".into(), name: "A test post".into(),
url: None, url: None,
body: None, body: None,
creator_id: inserted_person.id, creator_id: inserted_user.id,
community_id: inserted_community.id, community_id: inserted_community.id,
published: inserted_post.published, published: inserted_post.published,
removed: false, removed: false,
@ -364,7 +335,7 @@ mod tests {
// Post Like // Post Like
let post_like_form = PostLikeForm { let post_like_form = PostLikeForm {
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, user_id: inserted_user.id,
score: 1, score: 1,
}; };
@ -373,7 +344,7 @@ mod tests {
let expected_post_like = PostLike { let expected_post_like = PostLike {
id: inserted_post_like.id, id: inserted_post_like.id,
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, user_id: inserted_user.id,
published: inserted_post_like.published, published: inserted_post_like.published,
score: 1, score: 1,
}; };
@ -381,7 +352,7 @@ mod tests {
// Post Save // Post Save
let post_saved_form = PostSavedForm { let post_saved_form = PostSavedForm {
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, user_id: inserted_user.id,
}; };
let inserted_post_saved = PostSaved::save(&conn, &post_saved_form).unwrap(); let inserted_post_saved = PostSaved::save(&conn, &post_saved_form).unwrap();
@ -389,14 +360,14 @@ mod tests {
let expected_post_saved = PostSaved { let expected_post_saved = PostSaved {
id: inserted_post_saved.id, id: inserted_post_saved.id,
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, user_id: inserted_user.id,
published: inserted_post_saved.published, published: inserted_post_saved.published,
}; };
// Post Read // Post Read
let post_read_form = PostReadForm { let post_read_form = PostReadForm {
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, user_id: inserted_user.id,
}; };
let inserted_post_read = PostRead::mark_as_read(&conn, &post_read_form).unwrap(); let inserted_post_read = PostRead::mark_as_read(&conn, &post_read_form).unwrap();
@ -404,18 +375,18 @@ mod tests {
let expected_post_read = PostRead { let expected_post_read = PostRead {
id: inserted_post_read.id, id: inserted_post_read.id,
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, user_id: inserted_user.id,
published: inserted_post_read.published, published: inserted_post_read.published,
}; };
let read_post = Post::read(&conn, inserted_post.id).unwrap(); let read_post = Post::read(&conn, inserted_post.id).unwrap();
let updated_post = Post::update(&conn, inserted_post.id, &new_post).unwrap(); let updated_post = Post::update(&conn, inserted_post.id, &new_post).unwrap();
let like_removed = PostLike::remove(&conn, inserted_person.id, inserted_post.id).unwrap(); let like_removed = PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
let saved_removed = PostSaved::unsave(&conn, &post_saved_form).unwrap(); let saved_removed = PostSaved::unsave(&conn, &post_saved_form).unwrap();
let read_removed = PostRead::mark_as_unread(&conn, &post_read_form).unwrap(); let read_removed = PostRead::mark_as_unread(&conn, &post_read_form).unwrap();
let num_deleted = Post::delete(&conn, inserted_post.id).unwrap(); let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
Community::delete(&conn, inserted_community.id).unwrap(); Community::delete(&conn, inserted_community.id).unwrap();
Person::delete(&conn, inserted_person.id).unwrap(); User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(expected_post, read_post); assert_eq!(expected_post, read_post);
assert_eq!(expected_post, inserted_post); assert_eq!(expected_post, inserted_post);

View file

@ -1,6 +1,6 @@
use crate::Reportable; use crate::Reportable;
use diesel::{dsl::*, result::Error, *}; use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{naive_now, source::post_report::*, PersonId}; use lemmy_db_schema::{naive_now, source::post_report::*};
impl Reportable<PostReportForm> for PostReport { impl Reportable<PostReportForm> for PostReport {
/// creates a post report and returns it /// creates a post report and returns it
@ -19,11 +19,7 @@ impl Reportable<PostReportForm> for PostReport {
/// * `conn` - the postgres connection /// * `conn` - the postgres connection
/// * `report_id` - the id of the report to resolve /// * `report_id` - the id of the report to resolve
/// * `by_resolver_id` - the id of the user resolving the report /// * `by_resolver_id` - the id of the user resolving the report
fn resolve( fn resolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
conn: &PgConnection,
report_id: i32,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
use lemmy_db_schema::schema::post_report::dsl::*; use lemmy_db_schema::schema::post_report::dsl::*;
update(post_report.find(report_id)) update(post_report.find(report_id))
.set(( .set((
@ -39,11 +35,7 @@ impl Reportable<PostReportForm> for PostReport {
/// * `conn` - the postgres connection /// * `conn` - the postgres connection
/// * `report_id` - the id of the report to unresolve /// * `report_id` - the id of the report to unresolve
/// * `by_resolver_id` - the id of the user unresolving the report /// * `by_resolver_id` - the id of the user unresolving the report
fn unresolve( fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result<usize, Error> {
conn: &PgConnection,
report_id: i32,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
use lemmy_db_schema::schema::post_report::dsl::*; use lemmy_db_schema::schema::post_report::dsl::*;
update(post_report.find(report_id)) update(post_report.find(report_id))
.set(( .set((

View file

@ -1,9 +1,9 @@
use crate::{ApubObject, Crud}; use crate::{ApubObject, Crud};
use diesel::{dsl::*, result::Error, *}; use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{naive_now, source::private_message::*, DbUrl, PersonId, PrivateMessageId}; use lemmy_db_schema::{naive_now, source::private_message::*, Url};
impl Crud<PrivateMessageForm, PrivateMessageId> for PrivateMessage { impl Crud<PrivateMessageForm> for PrivateMessage {
fn read(conn: &PgConnection, private_message_id: PrivateMessageId) -> Result<Self, Error> { fn read(conn: &PgConnection, private_message_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::private_message::dsl::*; use lemmy_db_schema::schema::private_message::dsl::*;
private_message.find(private_message_id).first::<Self>(conn) private_message.find(private_message_id).first::<Self>(conn)
} }
@ -17,7 +17,7 @@ impl Crud<PrivateMessageForm, PrivateMessageId> for PrivateMessage {
fn update( fn update(
conn: &PgConnection, conn: &PgConnection,
private_message_id: PrivateMessageId, private_message_id: i32,
private_message_form: &PrivateMessageForm, private_message_form: &PrivateMessageForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use lemmy_db_schema::schema::private_message::dsl::*; use lemmy_db_schema::schema::private_message::dsl::*;
@ -28,7 +28,7 @@ impl Crud<PrivateMessageForm, PrivateMessageId> for PrivateMessage {
} }
impl ApubObject<PrivateMessageForm> for PrivateMessage { impl ApubObject<PrivateMessageForm> for PrivateMessage {
fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error> fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error>
where where
Self: Sized, Self: Sized,
{ {
@ -52,35 +52,35 @@ impl ApubObject<PrivateMessageForm> for PrivateMessage {
pub trait PrivateMessage_ { pub trait PrivateMessage_ {
fn update_ap_id( fn update_ap_id(
conn: &PgConnection, conn: &PgConnection,
private_message_id: PrivateMessageId, private_message_id: i32,
apub_id: DbUrl, apub_id: String,
) -> Result<PrivateMessage, Error>; ) -> Result<PrivateMessage, Error>;
fn update_content( fn update_content(
conn: &PgConnection, conn: &PgConnection,
private_message_id: PrivateMessageId, private_message_id: i32,
new_content: &str, new_content: &str,
) -> Result<PrivateMessage, Error>; ) -> Result<PrivateMessage, Error>;
fn update_deleted( fn update_deleted(
conn: &PgConnection, conn: &PgConnection,
private_message_id: PrivateMessageId, private_message_id: i32,
new_deleted: bool, new_deleted: bool,
) -> Result<PrivateMessage, Error>; ) -> Result<PrivateMessage, Error>;
fn update_read( fn update_read(
conn: &PgConnection, conn: &PgConnection,
private_message_id: PrivateMessageId, private_message_id: i32,
new_read: bool, new_read: bool,
) -> Result<PrivateMessage, Error>; ) -> Result<PrivateMessage, Error>;
fn mark_all_as_read( fn mark_all_as_read(
conn: &PgConnection, conn: &PgConnection,
for_recipient_id: PersonId, for_recipient_id: i32,
) -> Result<Vec<PrivateMessage>, Error>; ) -> Result<Vec<PrivateMessage>, Error>;
} }
impl PrivateMessage_ for PrivateMessage { impl PrivateMessage_ for PrivateMessage {
fn update_ap_id( fn update_ap_id(
conn: &PgConnection, conn: &PgConnection,
private_message_id: PrivateMessageId, private_message_id: i32,
apub_id: DbUrl, apub_id: String,
) -> Result<PrivateMessage, Error> { ) -> Result<PrivateMessage, Error> {
use lemmy_db_schema::schema::private_message::dsl::*; use lemmy_db_schema::schema::private_message::dsl::*;
@ -91,7 +91,7 @@ impl PrivateMessage_ for PrivateMessage {
fn update_content( fn update_content(
conn: &PgConnection, conn: &PgConnection,
private_message_id: PrivateMessageId, private_message_id: i32,
new_content: &str, new_content: &str,
) -> Result<PrivateMessage, Error> { ) -> Result<PrivateMessage, Error> {
use lemmy_db_schema::schema::private_message::dsl::*; use lemmy_db_schema::schema::private_message::dsl::*;
@ -102,7 +102,7 @@ impl PrivateMessage_ for PrivateMessage {
fn update_deleted( fn update_deleted(
conn: &PgConnection, conn: &PgConnection,
private_message_id: PrivateMessageId, private_message_id: i32,
new_deleted: bool, new_deleted: bool,
) -> Result<PrivateMessage, Error> { ) -> Result<PrivateMessage, Error> {
use lemmy_db_schema::schema::private_message::dsl::*; use lemmy_db_schema::schema::private_message::dsl::*;
@ -113,7 +113,7 @@ impl PrivateMessage_ for PrivateMessage {
fn update_read( fn update_read(
conn: &PgConnection, conn: &PgConnection,
private_message_id: PrivateMessageId, private_message_id: i32,
new_read: bool, new_read: bool,
) -> Result<PrivateMessage, Error> { ) -> Result<PrivateMessage, Error> {
use lemmy_db_schema::schema::private_message::dsl::*; use lemmy_db_schema::schema::private_message::dsl::*;
@ -124,7 +124,7 @@ impl PrivateMessage_ for PrivateMessage {
fn mark_all_as_read( fn mark_all_as_read(
conn: &PgConnection, conn: &PgConnection,
for_recipient_id: PersonId, for_recipient_id: i32,
) -> Result<Vec<PrivateMessage>, Error> { ) -> Result<Vec<PrivateMessage>, Error> {
use lemmy_db_schema::schema::private_message::dsl::*; use lemmy_db_schema::schema::private_message::dsl::*;
diesel::update( diesel::update(
@ -139,56 +139,76 @@ impl PrivateMessage_ for PrivateMessage {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{establish_unpooled_connection, source::private_message::PrivateMessage_, Crud}; use crate::{
use lemmy_db_schema::source::{person::*, private_message::*}; establish_unpooled_connection,
use serial_test::serial; source::private_message::PrivateMessage_,
Crud,
ListingType,
SortType,
};
use lemmy_db_schema::source::{private_message::*, user::*};
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let creator_form = PersonForm { let creator_form = UserForm {
name: "creator_pm".into(), name: "creator_pm".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_creator = Person::create(&conn, &creator_form).unwrap(); let inserted_creator = User_::create(&conn, &creator_form).unwrap();
let recipient_form = PersonForm { let recipient_form = UserForm {
name: "recipient_pm".into(), name: "recipient_pm".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_recipient = Person::create(&conn, &recipient_form).unwrap(); let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
let private_message_form = PrivateMessageForm { let private_message_form = PrivateMessageForm {
content: "A test private message".into(), content: "A test private message".into(),
@ -224,8 +244,8 @@ mod tests {
PrivateMessage::update_deleted(&conn, inserted_private_message.id, true).unwrap(); PrivateMessage::update_deleted(&conn, inserted_private_message.id, true).unwrap();
let marked_read_private_message = let marked_read_private_message =
PrivateMessage::update_read(&conn, inserted_private_message.id, true).unwrap(); PrivateMessage::update_read(&conn, inserted_private_message.id, true).unwrap();
Person::delete(&conn, inserted_creator.id).unwrap(); User_::delete(&conn, inserted_creator.id).unwrap();
Person::delete(&conn, inserted_recipient.id).unwrap(); User_::delete(&conn, inserted_recipient.id).unwrap();
assert_eq!(expected_private_message, read_private_message); assert_eq!(expected_private_message, read_private_message);
assert_eq!(expected_private_message, updated_private_message); assert_eq!(expected_private_message, updated_private_message);

View file

@ -1,8 +1,8 @@
use crate::Crud; use crate::Crud;
use diesel::{dsl::*, result::Error, *}; use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{naive_now, source::site::*, PersonId}; use lemmy_db_schema::{naive_now, source::site::*};
impl Crud<SiteForm, i32> for Site { impl Crud<SiteForm> for Site {
fn read(conn: &PgConnection, _site_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, _site_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::site::dsl::*; use lemmy_db_schema::schema::site::dsl::*;
site.first::<Self>(conn) site.first::<Self>(conn)
@ -26,12 +26,12 @@ impl Crud<SiteForm, i32> for Site {
} }
pub trait Site_ { pub trait Site_ {
fn transfer(conn: &PgConnection, new_creator_id: PersonId) -> Result<Site, Error>; fn transfer(conn: &PgConnection, new_creator_id: i32) -> Result<Site, Error>;
fn read_simple(conn: &PgConnection) -> Result<Site, Error>; fn read_simple(conn: &PgConnection) -> Result<Site, Error>;
} }
impl Site_ for Site { impl Site_ for Site {
fn transfer(conn: &PgConnection, new_creator_id: PersonId) -> Result<Site, Error> { fn transfer(conn: &PgConnection, new_creator_id: i32) -> Result<Site, Error> {
use lemmy_db_schema::schema::site::dsl::*; use lemmy_db_schema::schema::site::dsl::*;
diesel::update(site.find(1)) diesel::update(site.find(1))
.set((creator_id.eq(new_creator_id), updated.eq(naive_now()))) .set((creator_id.eq(new_creator_id), updated.eq(naive_now())))

View file

@ -0,0 +1,450 @@
use crate::{is_email_regex, ApubObject, Crud, ToSafeSettings};
use bcrypt::{hash, DEFAULT_COST};
use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{
naive_now,
schema::user_::dsl::*,
source::user::{UserForm, UserSafeSettings, User_},
Url,
};
use lemmy_utils::settings::Settings;
mod safe_type {
use crate::ToSafe;
use lemmy_db_schema::{schema::user_::columns::*, source::user::User_};
type Columns = (
id,
name,
preferred_username,
avatar,
admin,
banned,
published,
updated,
matrix_user_id,
actor_id,
bio,
local,
banner,
deleted,
);
impl ToSafe for User_ {
type SafeColumns = Columns;
fn safe_columns_tuple() -> Self::SafeColumns {
(
id,
name,
preferred_username,
avatar,
admin,
banned,
published,
updated,
matrix_user_id,
actor_id,
bio,
local,
banner,
deleted,
)
}
}
}
mod safe_type_alias_1 {
use crate::ToSafe;
use lemmy_db_schema::{schema::user_alias_1::columns::*, source::user::UserAlias1};
type Columns = (
id,
name,
preferred_username,
avatar,
admin,
banned,
published,
updated,
matrix_user_id,
actor_id,
bio,
local,
banner,
deleted,
);
impl ToSafe for UserAlias1 {
type SafeColumns = Columns;
fn safe_columns_tuple() -> Self::SafeColumns {
(
id,
name,
preferred_username,
avatar,
admin,
banned,
published,
updated,
matrix_user_id,
actor_id,
bio,
local,
banner,
deleted,
)
}
}
}
mod safe_type_alias_2 {
use crate::ToSafe;
use lemmy_db_schema::{schema::user_alias_2::columns::*, source::user::UserAlias2};
type Columns = (
id,
name,
preferred_username,
avatar,
admin,
banned,
published,
updated,
matrix_user_id,
actor_id,
bio,
local,
banner,
deleted,
);
impl ToSafe for UserAlias2 {
type SafeColumns = Columns;
fn safe_columns_tuple() -> Self::SafeColumns {
(
id,
name,
preferred_username,
avatar,
admin,
banned,
published,
updated,
matrix_user_id,
actor_id,
bio,
local,
banner,
deleted,
)
}
}
}
mod safe_settings_type {
use crate::ToSafeSettings;
use lemmy_db_schema::{schema::user_::columns::*, source::user::User_};
type Columns = (
id,
name,
preferred_username,
email,
avatar,
admin,
banned,
published,
updated,
show_nsfw,
theme,
default_sort_type,
default_listing_type,
lang,
show_avatars,
send_notifications_to_email,
matrix_user_id,
actor_id,
bio,
local,
last_refreshed_at,
banner,
deleted,
);
impl ToSafeSettings for User_ {
type SafeSettingsColumns = Columns;
fn safe_settings_columns_tuple() -> Self::SafeSettingsColumns {
(
id,
name,
preferred_username,
email,
avatar,
admin,
banned,
published,
updated,
show_nsfw,
theme,
default_sort_type,
default_listing_type,
lang,
show_avatars,
send_notifications_to_email,
matrix_user_id,
actor_id,
bio,
local,
last_refreshed_at,
banner,
deleted,
)
}
}
}
pub trait UserSafeSettings_ {
fn read(conn: &PgConnection, user_id: i32) -> Result<UserSafeSettings, Error>;
}
impl UserSafeSettings_ for UserSafeSettings {
fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
user_
.select(User_::safe_settings_columns_tuple())
.filter(deleted.eq(false))
.find(user_id)
.first::<Self>(conn)
}
}
impl Crud<UserForm> for User_ {
fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
user_
.filter(deleted.eq(false))
.find(user_id)
.first::<Self>(conn)
}
fn delete(conn: &PgConnection, user_id: i32) -> Result<usize, Error> {
diesel::delete(user_.find(user_id)).execute(conn)
}
fn create(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
insert_into(user_).values(form).get_result::<Self>(conn)
}
fn update(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result<Self, Error> {
diesel::update(user_.find(user_id))
.set(form)
.get_result::<Self>(conn)
}
}
impl ApubObject<UserForm> for User_ {
fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error> {
use lemmy_db_schema::schema::user_::dsl::*;
user_
.filter(deleted.eq(false))
.filter(actor_id.eq(object_id))
.first::<Self>(conn)
}
fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> {
insert_into(user_)
.values(user_form)
.on_conflict(actor_id)
.do_update()
.set(user_form)
.get_result::<Self>(conn)
}
}
pub trait User {
fn register(conn: &PgConnection, form: &UserForm) -> Result<User_, Error>;
fn update_password(conn: &PgConnection, user_id: i32, new_password: &str)
-> Result<User_, Error>;
fn read_from_name(conn: &PgConnection, from_user_name: &str) -> Result<User_, Error>;
fn add_admin(conn: &PgConnection, user_id: i32, added: bool) -> Result<User_, Error>;
fn ban_user(conn: &PgConnection, user_id: i32, ban: bool) -> Result<User_, Error>;
fn find_by_email_or_username(
conn: &PgConnection,
username_or_email: &str,
) -> Result<User_, Error>;
fn find_by_username(conn: &PgConnection, username: &str) -> Result<User_, Error>;
fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<User_, Error>;
fn get_profile_url(&self, hostname: &str) -> String;
fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error>;
fn delete_account(conn: &PgConnection, user_id: i32) -> Result<User_, Error>;
}
impl User for User_ {
fn register(conn: &PgConnection, form: &UserForm) -> 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)
}
// TODO do more individual updates like these
fn update_password(conn: &PgConnection, user_id: i32, new_password: &str) -> Result<Self, Error> {
let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
diesel::update(user_.find(user_id))
.set((
password_encrypted.eq(password_hash),
updated.eq(naive_now()),
))
.get_result::<Self>(conn)
}
fn read_from_name(conn: &PgConnection, from_user_name: &str) -> Result<Self, Error> {
user_
.filter(local.eq(true))
.filter(deleted.eq(false))
.filter(name.eq(from_user_name))
.first::<Self>(conn)
}
fn add_admin(conn: &PgConnection, user_id: i32, added: bool) -> Result<Self, Error> {
diesel::update(user_.find(user_id))
.set(admin.eq(added))
.get_result::<Self>(conn)
}
fn ban_user(conn: &PgConnection, user_id: i32, ban: bool) -> Result<Self, Error> {
diesel::update(user_.find(user_id))
.set(banned.eq(ban))
.get_result::<Self>(conn)
}
fn find_by_email_or_username(
conn: &PgConnection,
username_or_email: &str,
) -> Result<Self, Error> {
if is_email_regex(username_or_email) {
Self::find_by_email(conn, username_or_email)
} else {
Self::find_by_username(conn, username_or_email)
}
}
fn find_by_username(conn: &PgConnection, username: &str) -> Result<User_, Error> {
user_
.filter(deleted.eq(false))
.filter(local.eq(true))
.filter(name.ilike(username))
.first::<User_>(conn)
}
fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<User_, Error> {
user_
.filter(deleted.eq(false))
.filter(local.eq(true))
.filter(email.eq(from_email))
.first::<User_>(conn)
}
fn get_profile_url(&self, hostname: &str) -> String {
format!(
"{}://{}/u/{}",
Settings::get().get_protocol_string(),
hostname,
self.name
)
}
fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error> {
diesel::update(user_.find(user_id))
.set((last_refreshed_at.eq(naive_now()),))
.get_result::<Self>(conn)
}
fn delete_account(conn: &PgConnection, user_id: i32) -> Result<User_, Error> {
diesel::update(user_.find(user_id))
.set((
preferred_username.eq::<Option<String>>(None),
email.eq::<Option<String>>(None),
matrix_user_id.eq::<Option<String>>(None),
bio.eq::<Option<String>>(None),
deleted.eq(true),
updated.eq(naive_now()),
))
.get_result::<Self>(conn)
}
}
#[cfg(test)]
mod tests {
use crate::{establish_unpooled_connection, source::user::*, ListingType, SortType};
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
let new_user = UserForm {
name: "thommy".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None,
banner: None,
admin: false,
banned: Some(false),
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,
private_key: None,
public_key: None,
last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
let expected_user = User_ {
id: inserted_user.id,
name: "thommy".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None,
banner: None,
admin: false,
banned: false,
published: inserted_user.published,
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: inserted_user.actor_id.to_owned(),
bio: None,
local: true,
private_key: None,
public_key: None,
last_refreshed_at: inserted_user.published,
deleted: false,
};
let read_user = User_::read(&conn, inserted_user.id).unwrap();
let updated_user = User_::update(&conn, inserted_user.id, &new_user).unwrap();
let num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
assert_eq!(expected_user, read_user);
assert_eq!(expected_user, inserted_user);
assert_eq!(expected_user, updated_user);
assert_eq!(1, num_deleted);
}
}

View file

@ -1,68 +1,68 @@
use crate::Crud; use crate::Crud;
use diesel::{dsl::*, result::Error, *}; use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{source::person_mention::*, PersonId, PersonMentionId}; use lemmy_db_schema::source::user_mention::*;
impl Crud<PersonMentionForm, PersonMentionId> for PersonMention { impl Crud<UserMentionForm> for UserMention {
fn read(conn: &PgConnection, person_mention_id: PersonMentionId) -> Result<Self, Error> { fn read(conn: &PgConnection, user_mention_id: i32) -> Result<Self, Error> {
use lemmy_db_schema::schema::person_mention::dsl::*; use lemmy_db_schema::schema::user_mention::dsl::*;
person_mention.find(person_mention_id).first::<Self>(conn) user_mention.find(user_mention_id).first::<Self>(conn)
} }
fn create(conn: &PgConnection, person_mention_form: &PersonMentionForm) -> Result<Self, Error> { fn create(conn: &PgConnection, user_mention_form: &UserMentionForm) -> Result<Self, Error> {
use lemmy_db_schema::schema::person_mention::dsl::*; use lemmy_db_schema::schema::user_mention::dsl::*;
// since the return here isnt utilized, we dont need to do an update // since the return here isnt utilized, we dont need to do an update
// but get_result doesnt return the existing row here // but get_result doesnt return the existing row here
insert_into(person_mention) insert_into(user_mention)
.values(person_mention_form) .values(user_mention_form)
.on_conflict((recipient_id, comment_id)) .on_conflict((recipient_id, comment_id))
.do_update() .do_update()
.set(person_mention_form) .set(user_mention_form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn update( fn update(
conn: &PgConnection, conn: &PgConnection,
person_mention_id: PersonMentionId, user_mention_id: i32,
person_mention_form: &PersonMentionForm, user_mention_form: &UserMentionForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use lemmy_db_schema::schema::person_mention::dsl::*; use lemmy_db_schema::schema::user_mention::dsl::*;
diesel::update(person_mention.find(person_mention_id)) diesel::update(user_mention.find(user_mention_id))
.set(person_mention_form) .set(user_mention_form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
} }
pub trait PersonMention_ { pub trait UserMention_ {
fn update_read( fn update_read(
conn: &PgConnection, conn: &PgConnection,
person_mention_id: PersonMentionId, user_mention_id: i32,
new_read: bool, new_read: bool,
) -> Result<PersonMention, Error>; ) -> Result<UserMention, Error>;
fn mark_all_as_read( fn mark_all_as_read(
conn: &PgConnection, conn: &PgConnection,
for_recipient_id: PersonId, for_recipient_id: i32,
) -> Result<Vec<PersonMention>, Error>; ) -> Result<Vec<UserMention>, Error>;
} }
impl PersonMention_ for PersonMention { impl UserMention_ for UserMention {
fn update_read( fn update_read(
conn: &PgConnection, conn: &PgConnection,
person_mention_id: PersonMentionId, user_mention_id: i32,
new_read: bool, new_read: bool,
) -> Result<PersonMention, Error> { ) -> Result<UserMention, Error> {
use lemmy_db_schema::schema::person_mention::dsl::*; use lemmy_db_schema::schema::user_mention::dsl::*;
diesel::update(person_mention.find(person_mention_id)) diesel::update(user_mention.find(user_mention_id))
.set(read.eq(new_read)) .set(read.eq(new_read))
.get_result::<Self>(conn) .get_result::<Self>(conn)
} }
fn mark_all_as_read( fn mark_all_as_read(
conn: &PgConnection, conn: &PgConnection,
for_recipient_id: PersonId, for_recipient_id: i32,
) -> Result<Vec<PersonMention>, Error> { ) -> Result<Vec<UserMention>, Error> {
use lemmy_db_schema::schema::person_mention::dsl::*; use lemmy_db_schema::schema::user_mention::dsl::*;
diesel::update( diesel::update(
person_mention user_mention
.filter(recipient_id.eq(for_recipient_id)) .filter(recipient_id.eq(for_recipient_id))
.filter(read.eq(false)), .filter(read.eq(false)),
) )
@ -73,68 +73,83 @@ impl PersonMention_ for PersonMention {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{establish_unpooled_connection, Crud}; use crate::{establish_unpooled_connection, Crud, ListingType, SortType};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
comment::*, comment::*,
community::{Community, CommunityForm}, community::{Community, CommunityForm},
person::*,
person_mention::*,
post::*, post::*,
user::*,
user_mention::*,
}; };
use serial_test::serial;
#[test] #[test]
#[serial]
fn test_crud() { fn test_crud() {
let conn = establish_unpooled_connection(); let conn = establish_unpooled_connection();
let new_person = PersonForm { let new_user = UserForm {
name: "terrylake".into(), name: "terrylake".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_person = Person::create(&conn, &new_person).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
let recipient_form = PersonForm { let recipient_form = UserForm {
name: "terrylakes recipient".into(), name: "terrylakes recipient".into(),
preferred_username: None, preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None, avatar: None,
banner: None, banner: None,
banned: None, admin: false,
deleted: None, banned: Some(false),
published: None, published: None,
updated: 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, actor_id: None,
bio: None, bio: None,
local: None, local: true,
private_key: None, private_key: None,
public_key: None, public_key: None,
last_refreshed_at: None, last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_recipient = Person::create(&conn, &recipient_form).unwrap(); let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
let new_community = CommunityForm { let new_community = CommunityForm {
name: "test community lake".to_string(), name: "test community lake".to_string(),
title: "nada".to_owned(), title: "nada".to_owned(),
description: None, description: None,
creator_id: inserted_person.id, category_id: 1,
creator_id: inserted_user.id,
removed: None, removed: None,
deleted: None, deleted: None,
updated: None, updated: None,
@ -147,16 +162,13 @@ mod tests {
published: None, published: None,
icon: None, icon: None,
banner: None, banner: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
}; };
let inserted_community = Community::create(&conn, &new_community).unwrap(); let inserted_community = Community::create(&conn, &new_community).unwrap();
let new_post = PostForm { let new_post = PostForm {
name: "A test post".into(), name: "A test post".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
url: None, url: None,
body: None, body: None,
community_id: inserted_community.id, community_id: inserted_community.id,
@ -179,7 +191,7 @@ mod tests {
let comment_form = CommentForm { let comment_form = CommentForm {
content: "A test comment".into(), content: "A test comment".into(),
creator_id: inserted_person.id, creator_id: inserted_user.id,
post_id: inserted_post.id, post_id: inserted_post.id,
removed: None, removed: None,
deleted: None, deleted: None,
@ -193,15 +205,15 @@ mod tests {
let inserted_comment = Comment::create(&conn, &comment_form).unwrap(); let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
let person_mention_form = PersonMentionForm { let user_mention_form = UserMentionForm {
recipient_id: inserted_recipient.id, recipient_id: inserted_recipient.id,
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
read: None, read: None,
}; };
let inserted_mention = PersonMention::create(&conn, &person_mention_form).unwrap(); let inserted_mention = UserMention::create(&conn, &user_mention_form).unwrap();
let expected_mention = PersonMention { let expected_mention = UserMention {
id: inserted_mention.id, id: inserted_mention.id,
recipient_id: inserted_mention.recipient_id, recipient_id: inserted_mention.recipient_id,
comment_id: inserted_mention.comment_id, comment_id: inserted_mention.comment_id,
@ -209,14 +221,14 @@ mod tests {
published: inserted_mention.published, published: inserted_mention.published,
}; };
let read_mention = PersonMention::read(&conn, inserted_mention.id).unwrap(); let read_mention = UserMention::read(&conn, inserted_mention.id).unwrap();
let updated_mention = let updated_mention =
PersonMention::update(&conn, inserted_mention.id, &person_mention_form).unwrap(); UserMention::update(&conn, inserted_mention.id, &user_mention_form).unwrap();
Comment::delete(&conn, inserted_comment.id).unwrap(); Comment::delete(&conn, inserted_comment.id).unwrap();
Post::delete(&conn, inserted_post.id).unwrap(); Post::delete(&conn, inserted_post.id).unwrap();
Community::delete(&conn, inserted_community.id).unwrap(); Community::delete(&conn, inserted_community.id).unwrap();
Person::delete(&conn, inserted_person.id).unwrap(); User_::delete(&conn, inserted_user.id).unwrap();
Person::delete(&conn, inserted_recipient.id).unwrap(); User_::delete(&conn, inserted_recipient.id).unwrap();
assert_eq!(expected_mention, read_mention); assert_eq!(expected_mention, read_mention);
assert_eq!(expected_mention, inserted_mention); assert_eq!(expected_mention, inserted_mention);

View file

@ -3,14 +3,10 @@ name = "lemmy_db_schema"
version = "0.1.0" version = "0.1.0"
edition = "2018" edition = "2018"
[lib]
doctest = false
[dependencies] [dependencies]
diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","serde_json"] } diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","serde_json"] }
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.118", features = ["derive"] }
serde_json = { version = "1.0.61", features = ["preserve_order"] } serde_json = { version = "1.0.60", features = ["preserve_order"] }
log = "0.4.14" log = "0.4.11"
url = { version = "2.2.1", features = ["serde"] } url = { version = "2.2.0", features = ["serde"] }
diesel-derive-newtype = "0.1"

View file

@ -1,9 +1,6 @@
#[macro_use] #[macro_use]
extern crate diesel; extern crate diesel;
#[macro_use]
extern crate diesel_derive_newtype;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{ use diesel::{
backend::Backend, backend::Backend,
@ -11,62 +8,21 @@ use diesel::{
serialize::{Output, ToSql}, serialize::{Output, ToSql},
sql_types::Text, sql_types::Text,
}; };
use serde::{Deserialize, Serialize}; use serde::Serialize;
use std::{ use std::{
fmt,
fmt::{Display, Formatter}, fmt::{Display, Formatter},
io::Write, io::Write,
}; };
use url::Url;
pub mod schema; pub mod schema;
pub mod source; pub mod source;
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct PostId(pub i32);
impl fmt::Display for PostId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct PersonId(pub i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct CommentId(pub i32);
impl fmt::Display for CommentId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct CommunityId(pub i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct LocalUserId(pub i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct PrivateMessageId(i32);
impl fmt::Display for PrivateMessageId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct PersonMentionId(i32);
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, AsExpression, FromSqlRow)] #[derive(Clone, PartialEq, Serialize, Debug, AsExpression, FromSqlRow)]
#[sql_type = "Text"] #[sql_type = "Text"]
pub struct DbUrl(Url); pub struct Url(url::Url);
impl<DB: Backend> ToSql<Text, DB> for DbUrl impl<DB: Backend> ToSql<Text, DB> for Url
where where
String: ToSql<Text, DB>, String: ToSql<Text, DB>,
{ {
@ -75,37 +31,37 @@ where
} }
} }
impl<DB: Backend> FromSql<Text, DB> for DbUrl impl<DB: Backend> FromSql<Text, DB> for Url
where where
String: FromSql<Text, DB>, String: FromSql<Text, DB>,
{ {
fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result<Self> { fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result<Self> {
let str = String::from_sql(bytes)?; let str = String::from_sql(bytes)?;
Ok(DbUrl(Url::parse(&str)?)) Ok(Url(url::Url::parse(&str)?))
} }
} }
impl DbUrl { impl Url {
pub fn into_inner(self) -> Url { pub fn into_inner(self) -> url::Url {
self.0 self.0
} }
} }
impl Display for DbUrl { impl Display for Url {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.to_owned().into_inner().fmt(f) self.to_owned().into_inner().fmt(f)
} }
} }
impl From<DbUrl> for Url { impl From<Url> for url::Url {
fn from(url: DbUrl) -> Self { fn from(url: Url) -> Self {
url.0 url.0
} }
} }
impl From<Url> for DbUrl { impl From<url::Url> for Url {
fn from(url: Url) -> Self { fn from(url: url::Url) -> Self {
DbUrl(url) Url(url)
} }
} }

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