Merge branch 'federation'

This commit is contained in:
Dessalines 2020-07-01 09:05:25 -04:00
commit dbeb8d8da4
47 changed files with 3847 additions and 2735 deletions

View file

@ -20,6 +20,8 @@ services:
postgres: postgres:
image: postgres:12-alpine image: postgres:12-alpine
ports:
- "127.0.0.1:5432:5432"
environment: environment:
- POSTGRES_USER=lemmy - POSTGRES_USER=lemmy
- POSTGRES_PASSWORD=password - POSTGRES_PASSWORD=password

View file

@ -5,17 +5,21 @@ pushd ../../server/
cargo build cargo build
popd popd
pushd ../../ui
yarn
popd
mkdir -p volumes/pictrs_{alpha,beta,gamma}
sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma}
sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest
for Item in alpha beta gamma ; do sudo mkdir -p volumes/pictrs_alpha
sudo mkdir -p volumes/pictrs_$Item sudo chown -R 991:991 volumes/pictrs_alpha
sudo chown -R 991:991 volumes/pictrs_$Item
done
sudo docker-compose --file ../federation/docker-compose.yml --project-directory . up -d sudo docker-compose --file ../federation/docker-compose.yml --project-directory . up -d
pushd ../../ui pushd ../../ui
yarn
echo "Waiting for Lemmy to start..." echo "Waiting for Lemmy to start..."
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done

19
docker/federation-test/servers.sh vendored Executable file
View file

@ -0,0 +1,19 @@
#!/bin/bash
set -e
sudo rm -rf volumes
pushd ../../server/
cargo build
popd
pushd ../../ui
yarn
popd
mkdir -p volumes/pictrs_{alpha,beta,gamma}
sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma}
sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest
sudo docker-compose --file ../federation/docker-compose.yml --project-directory . up

10
docker/federation-test/tests.sh vendored Executable file
View file

@ -0,0 +1,10 @@
#!/bin/bash
set -xe
pushd ../../ui
echo "Waiting for Lemmy to start..."
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done
yarn api-test || true
popd

View file

@ -3,7 +3,7 @@ FROM ekidd/rust-musl-builder:1.42.0-openssl11
USER root USER root
RUN mkdir /app/dist/documentation/ -p \ RUN mkdir /app/dist/documentation/ -p \
&& addgroup --gid 1001 lemmy \ && addgroup --gid 1001 lemmy \
&& adduser --disabled-password --shell /bin/sh -u 1001 --ingroup lemmy lemmy && adduser --gecos "" --disabled-password --shell /bin/sh -u 1001 --ingroup lemmy lemmy
# Copy resources # Copy resources
COPY server/config/defaults.hjson /app/config/defaults.hjson COPY server/config/defaults.hjson /app/config/defaults.hjson

View file

@ -12,28 +12,33 @@ services:
- ../federation/nginx.conf:/etc/nginx/nginx.conf - ../federation/nginx.conf:/etc/nginx/nginx.conf
restart: on-failure restart: on-failure
depends_on: depends_on:
- lemmy_alpha - lemmy-alpha
- pictrs_alpha - pictrs
- lemmy_beta - lemmy-beta
- pictrs_beta - lemmy-gamma
- lemmy_gamma
- pictrs_gamma
- iframely - iframely
lemmy_alpha: pictrs:
restart: always
image: asonix/pictrs:v0.1.13-r0
user: 991:991
volumes:
- ./volumes/pictrs_alpha:/mnt
lemmy-alpha:
image: lemmy-federation:latest image: lemmy-federation:latest
environment: environment:
- LEMMY_HOSTNAME=lemmy_alpha:8540 - LEMMY_HOSTNAME=lemmy-alpha:8540
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_alpha:5432/lemmy - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_alpha:5432/lemmy
- LEMMY_JWT_SECRET=changeme - LEMMY_JWT_SECRET=changeme
- LEMMY_FRONT_END_DIR=/app/dist - LEMMY_FRONT_END_DIR=/app/dist
- LEMMY_FEDERATION__ENABLED=true - LEMMY_FEDERATION__ENABLED=true
- LEMMY_FEDERATION__TLS_ENABLED=false - LEMMY_FEDERATION__TLS_ENABLED=false
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy_beta,lemmy_gamma - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma
- LEMMY_PORT=8540 - LEMMY_PORT=8540
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha - LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy_alpha - LEMMY_SETUP__SITE_NAME=lemmy-alpha
- RUST_BACKTRACE=1 - RUST_BACKTRACE=1
- RUST_LOG=debug - RUST_LOG=debug
depends_on: depends_on:
@ -46,26 +51,21 @@ services:
- POSTGRES_DB=lemmy - POSTGRES_DB=lemmy
volumes: volumes:
- ./volumes/postgres_alpha:/var/lib/postgresql/data - ./volumes/postgres_alpha:/var/lib/postgresql/data
pictrs_alpha:
image: asonix/pictrs:v0.1.13-r0
user: 991:991
volumes:
- ./volumes/pictrs_alpha:/mnt
lemmy_beta: lemmy-beta:
image: lemmy-federation:latest image: lemmy-federation:latest
environment: environment:
- LEMMY_HOSTNAME=lemmy_beta:8550 - LEMMY_HOSTNAME=lemmy-beta:8550
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_beta:5432/lemmy - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_beta:5432/lemmy
- LEMMY_JWT_SECRET=changeme - LEMMY_JWT_SECRET=changeme
- LEMMY_FRONT_END_DIR=/app/dist - LEMMY_FRONT_END_DIR=/app/dist
- LEMMY_FEDERATION__ENABLED=true - LEMMY_FEDERATION__ENABLED=true
- LEMMY_FEDERATION__TLS_ENABLED=false - LEMMY_FEDERATION__TLS_ENABLED=false
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy_alpha,lemmy_gamma - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma
- LEMMY_PORT=8550 - LEMMY_PORT=8550
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta - LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy_beta - LEMMY_SETUP__SITE_NAME=lemmy-beta
- RUST_BACKTRACE=1 - RUST_BACKTRACE=1
- RUST_LOG=debug - RUST_LOG=debug
depends_on: depends_on:
@ -78,26 +78,21 @@ services:
- POSTGRES_DB=lemmy - POSTGRES_DB=lemmy
volumes: volumes:
- ./volumes/postgres_beta:/var/lib/postgresql/data - ./volumes/postgres_beta:/var/lib/postgresql/data
pictrs_beta:
image: asonix/pictrs:v0.1.13-r0
user: 991:991
volumes:
- ./volumes/pictrs_beta:/mnt
lemmy_gamma: lemmy-gamma:
image: lemmy-federation:latest image: lemmy-federation:latest
environment: environment:
- LEMMY_HOSTNAME=lemmy_gamma:8560 - LEMMY_HOSTNAME=lemmy-gamma:8560
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_gamma:5432/lemmy - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_gamma:5432/lemmy
- LEMMY_JWT_SECRET=changeme - LEMMY_JWT_SECRET=changeme
- LEMMY_FRONT_END_DIR=/app/dist - LEMMY_FRONT_END_DIR=/app/dist
- LEMMY_FEDERATION__ENABLED=true - LEMMY_FEDERATION__ENABLED=true
- LEMMY_FEDERATION__TLS_ENABLED=false - LEMMY_FEDERATION__TLS_ENABLED=false
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy_alpha,lemmy_beta - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta
- LEMMY_PORT=8560 - LEMMY_PORT=8560
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma - LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy_gamma - LEMMY_SETUP__SITE_NAME=lemmy-gamma
- RUST_BACKTRACE=1 - RUST_BACKTRACE=1
- RUST_LOG=debug - RUST_LOG=debug
depends_on: depends_on:
@ -110,11 +105,6 @@ services:
- POSTGRES_DB=lemmy - POSTGRES_DB=lemmy
volumes: volumes:
- ./volumes/postgres_gamma:/var/lib/postgresql/data - ./volumes/postgres_gamma:/var/lib/postgresql/data
pictrs_gamma:
image: asonix/pictrs:v0.1.13-r0
user: 991:991
volumes:
- ./volumes/pictrs_gamma:/mnt
iframely: iframely:
image: jolt/iframely:v1.4.3 image: jolt/iframely:v1.4.3

View file

@ -12,7 +12,7 @@ http {
client_max_body_size 50M; client_max_body_size 50M;
location / { location / {
proxy_pass http://lemmy_alpha:8540; proxy_pass http://lemmy-alpha:8540;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -26,7 +26,7 @@ http {
# pict-rs images # pict-rs images
location /pictrs { location /pictrs {
location /pictrs/image { location /pictrs/image {
proxy_pass http://pictrs_alpha:8080/image; proxy_pass http://pictrs:8080/image;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -52,7 +52,7 @@ http {
client_max_body_size 50M; client_max_body_size 50M;
location / { location / {
proxy_pass http://lemmy_beta:8550; proxy_pass http://lemmy-beta:8550;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -66,7 +66,7 @@ http {
# pict-rs images # pict-rs images
location /pictrs { location /pictrs {
location /pictrs/image { location /pictrs/image {
proxy_pass http://pictrs_beta:8080/image; proxy_pass http://pictrs:8080/image;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -92,7 +92,7 @@ http {
client_max_body_size 50M; client_max_body_size 50M;
location / { location / {
proxy_pass http://lemmy_gamma:8560; proxy_pass http://lemmy-gamma:8560;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -106,7 +106,7 @@ http {
# pict-rs images # pict-rs images
location /pictrs { location /pictrs {
location /pictrs/image { location /pictrs/image {
proxy_pass http://pictrs_gamma:8080/image; proxy_pass http://pictrs:8080/image;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

569
server/Cargo.lock generated vendored

File diff suppressed because it is too large Load diff

15
server/Cargo.toml vendored
View file

@ -19,11 +19,12 @@ chrono = { version = "0.4.7", features = ["serde"] }
serde_json = { version = "1.0.52", features = ["preserve_order"]} serde_json = { version = "1.0.52", features = ["preserve_order"]}
failure = "0.1.8" failure = "0.1.8"
serde = { version = "1.0.105", features = ["derive"] } serde = { version = "1.0.105", features = ["derive"] }
actix = "0.9.0" actix = "0.10.0-alpha.2"
actix-web = "2.0.0" actix-web = { version = "3.0.0-alpha.3", features = ["rustls"] }
actix-files = "0.2.1" actix-files = "0.3.0-alpha.1"
actix-web-actors = "2.0.0" actix-web-actors = "3.0.0-alpha.1"
actix-rt = "1.1.1" actix-rt = "1.1.1"
awc = "2.0.0-alpha.2"
log = "0.4.0" log = "0.4.0"
env_logger = "0.7.1" env_logger = "0.7.1"
rand = "0.7.3" rand = "0.7.3"
@ -34,19 +35,19 @@ regex = "1.3.5"
lazy_static = "1.3.0" lazy_static = "1.3.0"
lettre = "0.9.3" lettre = "0.9.3"
lettre_email = "0.9.4" lettre_email = "0.9.4"
sha2 = "0.8.1"
rss = "1.9.0" rss = "1.9.0"
htmlescape = "0.3.1" htmlescape = "0.3.1"
url = { version = "2.1.1", features = ["serde"] } url = { version = "2.1.1", features = ["serde"] }
config = {version = "0.10.1", default-features = false, features = ["hjson"] } config = {version = "0.10.1", default-features = false, features = ["hjson"] }
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
isahc = "0.9.2"
comrak = "0.7" comrak = "0.7"
openssl = "0.10" openssl = "0.10"
http = "0.2.1" http = "0.2.1"
http-signature-normalization = "0.5.1" http-signature-normalization-actix = { version = "0.4.0-alpha.0", default-features = false, features = ["sha-2"] }
base64 = "0.12.1" base64 = "0.12.1"
tokio = "0.2.21" tokio = "0.2.21"
futures = "0.3.5" futures = "0.3.5"
itertools = "0.9.0" itertools = "0.9.0"
uuid = { version = "0.8", features = ["serde", "v4"] } uuid = { version = "0.8", features = ["serde", "v4"] }
sha2 = "0.9"
async-trait = "0.1.36"

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
api::{APIError, Oper, Perform}, api::{APIError, Oper, Perform},
apub::{ApubLikeableType, ApubObjectType}, apub::{ApubLikeableType, ApubObjectType},
blocking,
db::{ db::{
comment::*, comment::*,
comment_view::*, comment_view::*,
@ -27,13 +28,10 @@ use crate::{
UserOperation, UserOperation,
WebsocketInfo, WebsocketInfo,
}, },
DbPool,
LemmyError,
MentionData, MentionData,
}; };
use diesel::{
r2d2::{ConnectionManager, Pool},
PgConnection,
};
use failure::Error;
use log::error; use log::error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr; use std::str::FromStr;
@ -97,14 +95,15 @@ pub struct GetCommentsResponse {
comments: Vec<CommentView>, comments: Vec<CommentView>,
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<CreateComment> { impl Perform for Oper<CreateComment> {
type Response = CommentResponse; type Response = CommentResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<CommentResponse, Error> { ) -> Result<CommentResponse, LemmyError> {
let data: &CreateComment = &self.data; let data: &CreateComment = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -114,20 +113,6 @@ impl Perform for Oper<CreateComment> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?;
// Check for a community ban
let post = Post::read(&conn, data.post_id)?;
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() {
return Err(APIError::err("community_ban").into());
}
// Check for a site ban
let user = User_::read(&conn, user_id)?;
if user.banned {
return Err(APIError::err("site_ban").into());
}
let content_slurs_removed = remove_slurs(&data.content.to_owned()); let content_slurs_removed = remove_slurs(&data.content.to_owned());
let comment_form = CommentForm { let comment_form = CommentForm {
@ -144,21 +129,48 @@ impl Perform for Oper<CreateComment> {
local: true, local: true,
}; };
let inserted_comment = match Comment::create(&conn, &comment_form) { // Check for a community ban
let post_id = data.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let is_banned =
move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
if blocking(pool, is_banned).await? {
return Err(APIError::err("community_ban").into());
}
// Check for a site ban
let user = blocking(pool, move |conn| User_::read(&conn, user_id)).await??;
if user.banned {
return Err(APIError::err("site_ban").into());
}
let comment_form2 = comment_form.clone();
let inserted_comment =
match blocking(pool, move |conn| Comment::create(&conn, &comment_form2)).await? {
Ok(comment) => comment,
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
};
let inserted_comment_id = inserted_comment.id;
let updated_comment: Comment = match blocking(pool, move |conn| {
Comment::update_ap_id(&conn, inserted_comment_id)
})
.await?
{
Ok(comment) => comment, Ok(comment) => comment,
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()), Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
}; };
let updated_comment = match Comment::update_ap_id(&conn, inserted_comment.id) { updated_comment
Ok(comment) => comment, .send_create(&user, &self.client, pool)
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()), .await?;
};
updated_comment.send_create(&user, &conn)?;
// Scan the comment for user mentions, add those rows // Scan the comment for user mentions, add those rows
let mentions = scrape_text_for_mentions(&comment_form.content); let mentions = scrape_text_for_mentions(&comment_form.content);
let recipient_ids = send_local_notifs(&conn, &mentions, &updated_comment, &user, &post); let recipient_ids =
send_local_notifs(mentions, updated_comment.clone(), user.clone(), post, pool).await?;
// You like your own comment by default // You like your own comment by default
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
@ -168,14 +180,17 @@ impl Perform for Oper<CreateComment> {
score: 1, score: 1,
}; };
let _inserted_like = match CommentLike::like(&conn, &like_form) { let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
Ok(like) => like, if blocking(pool, like).await?.is_err() {
Err(_e) => return Err(APIError::err("couldnt_like_comment").into()), return Err(APIError::err("couldnt_like_comment").into());
}; }
updated_comment.send_like(&user, &conn)?; updated_comment.send_like(&user, &self.client, pool).await?;
let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id))?; let comment_view = blocking(pool, move |conn| {
CommentView::read(&conn, inserted_comment.id, Some(user_id))
})
.await??;
let mut res = CommentResponse { let mut res = CommentResponse {
comment: comment_view, comment: comment_view,
@ -198,14 +213,15 @@ impl Perform for Oper<CreateComment> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<EditComment> { impl Perform for Oper<EditComment> {
type Response = CommentResponse; type Response = CommentResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<CommentResponse, Error> { ) -> Result<CommentResponse, LemmyError> {
let data: &EditComment = &self.data; let data: &EditComment = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -215,30 +231,44 @@ impl Perform for Oper<EditComment> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?; let user = blocking(pool, move |conn| User_::read(&conn, user_id)).await??;
let user = User_::read(&conn, user_id)?; let edit_id = data.edit_id;
let orig_comment =
let orig_comment = CommentView::read(&conn, data.edit_id, None)?; blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??;
// You are allowed to mark the comment as read even if you're banned. // You are allowed to mark the comment as read even if you're banned.
if data.read.is_none() { if data.read.is_none() {
// Verify its the creator or a mod, or an admin // Verify its the creator or a mod, or an admin
let mut editors: Vec<i32> = vec![data.creator_id]; let mut editors: Vec<i32> = vec![data.creator_id];
let community_id = orig_comment.community_id;
editors.append( editors.append(
&mut CommunityModeratorView::for_community(&conn, orig_comment.community_id)? &mut blocking(pool, move |conn| {
.into_iter() Ok(
.map(|m| m.user_id) CommunityModeratorView::for_community(&conn, community_id)?
.collect(), .into_iter()
.map(|m| m.user_id)
.collect(),
) as Result<_, LemmyError>
})
.await??,
);
editors.append(
&mut blocking(pool, move |conn| {
Ok(UserView::admins(conn)?.into_iter().map(|a| a.id).collect()) as Result<_, LemmyError>
})
.await??,
); );
editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
if !editors.contains(&user_id) { if !editors.contains(&user_id) {
return Err(APIError::err("no_comment_edit_allowed").into()); return Err(APIError::err("no_comment_edit_allowed").into());
} }
// Check for a community ban // Check for a community ban
if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() { let community_id = orig_comment.community_id;
let is_banned =
move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
if blocking(pool, is_banned).await? {
return Err(APIError::err("community_ban").into()); return Err(APIError::err("community_ban").into());
} }
@ -250,7 +280,8 @@ impl Perform for Oper<EditComment> {
let content_slurs_removed = remove_slurs(&data.content.to_owned()); let content_slurs_removed = remove_slurs(&data.content.to_owned());
let read_comment = Comment::read(&conn, data.edit_id)?; let edit_id = data.edit_id;
let read_comment = blocking(pool, move |conn| Comment::read(conn, edit_id)).await??;
let comment_form = CommentForm { let comment_form = CommentForm {
content: content_slurs_removed, content: content_slurs_removed,
@ -270,31 +301,48 @@ impl Perform for Oper<EditComment> {
local: read_comment.local, local: read_comment.local,
}; };
let updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) { let edit_id = data.edit_id;
let comment_form2 = comment_form.clone();
let updated_comment = match blocking(pool, move |conn| {
Comment::update(conn, edit_id, &comment_form2)
})
.await?
{
Ok(comment) => comment, Ok(comment) => comment,
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
}; };
if let Some(deleted) = data.deleted.to_owned() { if let Some(deleted) = data.deleted.to_owned() {
if deleted { if deleted {
updated_comment.send_delete(&user, &conn)?; updated_comment
.send_delete(&user, &self.client, pool)
.await?;
} else { } else {
updated_comment.send_undo_delete(&user, &conn)?; updated_comment
.send_undo_delete(&user, &self.client, pool)
.await?;
} }
} else if let Some(removed) = data.removed.to_owned() { } else if let Some(removed) = data.removed.to_owned() {
if removed { if removed {
updated_comment.send_remove(&user, &conn)?; updated_comment
.send_remove(&user, &self.client, pool)
.await?;
} else { } else {
updated_comment.send_undo_remove(&user, &conn)?; updated_comment
.send_undo_remove(&user, &self.client, pool)
.await?;
} }
} else { } else {
updated_comment.send_update(&user, &conn)?; updated_comment
.send_update(&user, &self.client, pool)
.await?;
} }
let post = Post::read(&conn, data.post_id)?; let post_id = data.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let mentions = scrape_text_for_mentions(&comment_form.content); let mentions = scrape_text_for_mentions(&comment_form.content);
let recipient_ids = send_local_notifs(&conn, &mentions, &updated_comment, &user, &post); let recipient_ids = send_local_notifs(mentions, updated_comment, user, post, pool).await?;
// Mod tables // Mod tables
if let Some(removed) = data.removed.to_owned() { if let Some(removed) = data.removed.to_owned() {
@ -304,10 +352,14 @@ impl Perform for Oper<EditComment> {
removed: Some(removed), removed: Some(removed),
reason: data.reason.to_owned(), reason: data.reason.to_owned(),
}; };
ModRemoveComment::create(&conn, &form)?; blocking(pool, move |conn| ModRemoveComment::create(conn, &form)).await??;
} }
let comment_view = CommentView::read(&conn, data.edit_id, Some(user_id))?; let edit_id = data.edit_id;
let comment_view = blocking(pool, move |conn| {
CommentView::read(conn, edit_id, Some(user_id))
})
.await??;
let mut res = CommentResponse { let mut res = CommentResponse {
comment: comment_view, comment: comment_view,
@ -330,14 +382,15 @@ impl Perform for Oper<EditComment> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<SaveComment> { impl Perform for Oper<SaveComment> {
type Response = CommentResponse; type Response = CommentResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<CommentResponse, Error> { ) -> Result<CommentResponse, LemmyError> {
let data: &SaveComment = &self.data; let data: &SaveComment = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -352,21 +405,23 @@ impl Perform for Oper<SaveComment> {
user_id, user_id,
}; };
let conn = pool.get()?;
if data.save { if data.save {
match CommentSaved::save(&conn, &comment_saved_form) { let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
Ok(comment) => comment, if blocking(pool, save_comment).await?.is_err() {
Err(_e) => return Err(APIError::err("couldnt_save_comment").into()), return Err(APIError::err("couldnt_save_comment").into());
}; }
} else { } else {
match CommentSaved::unsave(&conn, &comment_saved_form) { let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
Ok(comment) => comment, if blocking(pool, unsave_comment).await?.is_err() {
Err(_e) => return Err(APIError::err("couldnt_save_comment").into()), return Err(APIError::err("couldnt_save_comment").into());
}; }
} }
let comment_view = CommentView::read(&conn, data.comment_id, Some(user_id))?; let comment_id = data.comment_id;
let comment_view = blocking(pool, move |conn| {
CommentView::read(conn, comment_id, Some(user_id))
})
.await??;
Ok(CommentResponse { Ok(CommentResponse {
comment: comment_view, comment: comment_view,
@ -375,14 +430,15 @@ impl Perform for Oper<SaveComment> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<CreateCommentLike> { impl Perform for Oper<CreateCommentLike> {
type Response = CommentResponse; type Response = CommentResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<CommentResponse, Error> { ) -> Result<CommentResponse, LemmyError> {
let data: &CreateCommentLike = &self.data; let data: &CreateCommentLike = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -394,36 +450,42 @@ impl Perform for Oper<CreateCommentLike> {
let mut recipient_ids = Vec::new(); let mut recipient_ids = Vec::new();
let conn = pool.get()?;
// Don't do a downvote if site has downvotes disabled // Don't do a downvote if site has downvotes disabled
if data.score == -1 { if data.score == -1 {
let site = SiteView::read(&conn)?; let site = blocking(pool, move |conn| SiteView::read(conn)).await??;
if !site.enable_downvotes { if !site.enable_downvotes {
return Err(APIError::err("downvotes_disabled").into()); return Err(APIError::err("downvotes_disabled").into());
} }
} }
// Check for a community ban // Check for a community ban
let post = Post::read(&conn, data.post_id)?; let post_id = data.post_id;
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let is_banned =
move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
if blocking(pool, is_banned).await? {
return Err(APIError::err("community_ban").into()); return Err(APIError::err("community_ban").into());
} }
// Check for a site ban // Check for a site ban
let user = User_::read(&conn, user_id)?; let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
if user.banned { if user.banned {
return Err(APIError::err("site_ban").into()); return Err(APIError::err("site_ban").into());
} }
let comment = Comment::read(&conn, data.comment_id)?; let comment_id = data.comment_id;
let comment = blocking(pool, move |conn| Comment::read(conn, comment_id)).await??;
// Add to recipient ids // Add to recipient ids
match comment.parent_id { match comment.parent_id {
Some(parent_id) => { Some(parent_id) => {
let parent_comment = Comment::read(&conn, parent_id)?; let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??;
if parent_comment.creator_id != user_id { if parent_comment.creator_id != user_id {
let parent_user = User_::read(&conn, parent_comment.creator_id)?; let parent_user = blocking(pool, move |conn| {
User_::read(conn, parent_comment.creator_id)
})
.await??;
recipient_ids.push(parent_user.id); recipient_ids.push(parent_user.id);
} }
} }
@ -440,27 +502,33 @@ impl Perform for Oper<CreateCommentLike> {
}; };
// Remove any likes first // Remove any likes first
CommentLike::remove(&conn, &like_form)?; let like_form2 = like_form.clone();
blocking(pool, move |conn| CommentLike::remove(conn, &like_form2)).await??;
// Only add the like if the score isnt 0 // Only add the like if the score isnt 0
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
if do_add { if do_add {
let _inserted_like = match CommentLike::like(&conn, &like_form) { let like_form2 = like_form.clone();
Ok(like) => like, let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
Err(_e) => return Err(APIError::err("couldnt_like_comment").into()), if blocking(pool, like).await?.is_err() {
}; return Err(APIError::err("couldnt_like_comment").into());
}
if like_form.score == 1 { if like_form.score == 1 {
comment.send_like(&user, &conn)?; comment.send_like(&user, &self.client, pool).await?;
} else if like_form.score == -1 { } else if like_form.score == -1 {
comment.send_dislike(&user, &conn)?; comment.send_dislike(&user, &self.client, pool).await?;
} }
} else { } else {
comment.send_undo_like(&user, &conn)?; comment.send_undo_like(&user, &self.client, pool).await?;
} }
// Have to refetch the comment to get the current state // Have to refetch the comment to get the current state
let liked_comment = CommentView::read(&conn, data.comment_id, Some(user_id))?; let comment_id = data.comment_id;
let liked_comment = blocking(pool, move |conn| {
CommentView::read(conn, comment_id, Some(user_id))
})
.await??;
let mut res = CommentResponse { let mut res = CommentResponse {
comment: liked_comment, comment: liked_comment,
@ -483,14 +551,15 @@ impl Perform for Oper<CreateCommentLike> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<GetComments> { impl Perform for Oper<GetComments> {
type Response = GetCommentsResponse; type Response = GetCommentsResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<GetCommentsResponse, Error> { ) -> Result<GetCommentsResponse, LemmyError> {
let data: &GetComments = &self.data; let data: &GetComments = &self.data;
let user_claims: Option<Claims> = match &data.auth { let user_claims: Option<Claims> = match &data.auth {
@ -509,19 +578,23 @@ impl Perform for Oper<GetComments> {
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)?;
let conn = pool.get()?; let community_id = data.community_id;
let page = data.page;
let comments = match CommentQueryBuilder::create(&conn) let limit = data.limit;
.listing_type(type_) let comments = blocking(pool, move |conn| {
.sort(&sort) CommentQueryBuilder::create(conn)
.for_community_id(data.community_id) .listing_type(type_)
.my_user_id(user_id) .sort(&sort)
.page(data.page) .for_community_id(community_id)
.limit(data.limit) .my_user_id(user_id)
.list() .page(page)
{ .limit(limit)
.list()
})
.await?;
let comments = match comments {
Ok(comments) => comments, Ok(comments) => comments,
Err(_e) => return Err(APIError::err("couldnt_get_comments").into()), Err(_) => return Err(APIError::err("couldnt_get_comments").into()),
}; };
if let Some(ws) = websocket_info { if let Some(ws) = websocket_info {
@ -542,8 +615,23 @@ impl Perform for Oper<GetComments> {
} }
} }
pub fn send_local_notifs( pub async fn send_local_notifs(
conn: &PgConnection, mentions: Vec<MentionData>,
comment: Comment,
user: User_,
post: Post,
pool: &DbPool,
) -> Result<Vec<i32>, LemmyError> {
let ids = blocking(pool, move |conn| {
do_send_local_notifs(conn, &mentions, &comment, &user, &post)
})
.await?;
Ok(ids)
}
fn do_send_local_notifs(
conn: &diesel::PgConnection,
mentions: &[MentionData], mentions: &[MentionData],
comment: &Comment, comment: &Comment,
user: &User_, user: &User_,

View file

@ -7,6 +7,7 @@ use crate::{
ActorType, ActorType,
EndpointType, EndpointType,
}, },
blocking,
db::{Bannable, Crud, Followable, Joinable, SortType}, db::{Bannable, Crud, Followable, Joinable, SortType},
is_valid_community_name, is_valid_community_name,
naive_from_unix, naive_from_unix,
@ -18,12 +19,9 @@ use crate::{
UserOperation, UserOperation,
WebsocketInfo, WebsocketInfo,
}, },
DbPool,
LemmyError,
}; };
use diesel::{
r2d2::{ConnectionManager, Pool},
PgConnection,
};
use failure::Error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr; use std::str::FromStr;
@ -138,14 +136,15 @@ pub struct TransferCommunity {
auth: String, auth: String,
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<GetCommunity> { impl Perform for Oper<GetCommunity> {
type Response = GetCommunityResponse; type Response = GetCommunityResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<GetCommunityResponse, Error> { ) -> Result<GetCommunityResponse, LemmyError> {
let data: &GetCommunity = &self.data; let data: &GetCommunity = &self.data;
let user_id: Option<i32> = match &data.auth { let user_id: Option<i32> = match &data.auth {
@ -159,33 +158,38 @@ impl Perform for Oper<GetCommunity> {
None => None, None => None,
}; };
let conn = pool.get()?; let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
let community = match data.id { let community = match data.id {
Some(id) => Community::read(&conn, id)?, Some(id) => blocking(pool, move |conn| Community::read(conn, id)).await??,
None => { None => match blocking(pool, move |conn| Community::read_from_name(conn, &name)).await? {
match Community::read_from_name( Ok(community) => community,
&conn, Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
&data.name.to_owned().unwrap_or_else(|| "main".to_string()), },
) {
Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}
}
}; };
let community_view = match CommunityView::read(&conn, community.id, user_id) { let community_id = community.id;
let community_view = match blocking(pool, move |conn| {
CommunityView::read(conn, community_id, user_id)
})
.await?
{
Ok(community) => community, 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 = match CommunityModeratorView::for_community(&conn, community.id) { let community_id = community.id;
let moderators: Vec<CommunityModeratorView> = match blocking(pool, move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.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 site_creator_id = Site::read(&conn, 1)?.creator_id; let site = blocking(pool, move |conn| Site::read(conn, 1)).await??;
let mut admins = UserView::admins(&conn)?; let site_creator_id = site.creator_id;
let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap(); let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
let creator_user = admins.remove(creator_index); let creator_user = admins.remove(creator_index);
admins.insert(0, creator_user); admins.insert(0, creator_user);
@ -220,14 +224,15 @@ impl Perform for Oper<GetCommunity> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<CreateCommunity> { impl Perform for Oper<CreateCommunity> {
type Response = CommunityResponse; type Response = CommunityResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<CommunityResponse, Error> { ) -> Result<CommunityResponse, LemmyError> {
let data: &CreateCommunity = &self.data; let data: &CreateCommunity = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -255,10 +260,9 @@ impl Perform for Oper<CreateCommunity> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?;
// Check for a site ban // Check for a site ban
if UserView::read(&conn, user_id)?.banned { let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
if user_view.banned {
return Err(APIError::err("site_ban").into()); return Err(APIError::err("site_ban").into());
} }
@ -283,34 +287,36 @@ impl Perform for Oper<CreateCommunity> {
published: None, published: None,
}; };
let inserted_community = match Community::create(&conn, &community_form) { let inserted_community =
Ok(community) => community, match blocking(pool, move |conn| Community::create(conn, &community_form)).await? {
Err(_e) => return Err(APIError::err("community_already_exists").into()), Ok(community) => community,
}; Err(_e) => return Err(APIError::err("community_already_exists").into()),
};
let community_moderator_form = CommunityModeratorForm { let community_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id, community_id: inserted_community.id,
user_id, user_id,
}; };
let _inserted_community_moderator = let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
match CommunityModerator::join(&conn, &community_moderator_form) { if blocking(pool, join).await?.is_err() {
Ok(user) => user, return Err(APIError::err("community_moderator_already_exists").into());
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), }
};
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id, community_id: inserted_community.id,
user_id, user_id,
}; };
let _inserted_community_follower = let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
match CommunityFollower::follow(&conn, &community_follower_form) { if blocking(pool, follow).await?.is_err() {
Ok(user) => user, return Err(APIError::err("community_follower_already_exists").into());
Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), }
};
let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?; let community_view = blocking(pool, move |conn| {
CommunityView::read(conn, inserted_community.id, Some(user_id))
})
.await??;
Ok(CommunityResponse { Ok(CommunityResponse {
community: community_view, community: community_view,
@ -318,14 +324,15 @@ impl Perform for Oper<CreateCommunity> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<EditCommunity> { impl Perform for Oper<EditCommunity> {
type Response = CommunityResponse; type Response = CommunityResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<CommunityResponse, Error> { ) -> Result<CommunityResponse, LemmyError> {
let data: &EditCommunity = &self.data; let data: &EditCommunity = &self.data;
if let Err(slurs) = slur_check(&data.name) { if let Err(slurs) = slur_check(&data.name) {
@ -353,28 +360,34 @@ impl Perform for Oper<EditCommunity> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?;
// Check for a site ban // Check for a site ban
let user = User_::read(&conn, user_id)?; let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
if user.banned { if user.banned {
return Err(APIError::err("site_ban").into()); return Err(APIError::err("site_ban").into());
} }
// Verify its a mod // Verify its a mod
let edit_id = data.edit_id;
let mut editors: Vec<i32> = Vec::new(); let mut editors: Vec<i32> = Vec::new();
editors.append( editors.append(
&mut CommunityModeratorView::for_community(&conn, data.edit_id)? &mut blocking(pool, move |conn| {
.into_iter() CommunityModeratorView::for_community(conn, edit_id)
.map(|m| m.user_id) .map(|v| v.into_iter().map(|m| m.user_id).collect())
.collect(), })
.await??,
);
editors.append(
&mut blocking(pool, move |conn| {
UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())
})
.await??,
); );
editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
if !editors.contains(&user_id) { if !editors.contains(&user_id) {
return Err(APIError::err("no_community_edit_allowed").into()); return Err(APIError::err("no_community_edit_allowed").into());
} }
let read_community = Community::read(&conn, data.edit_id)?; let edit_id = data.edit_id;
let read_community = blocking(pool, move |conn| Community::read(conn, edit_id)).await??;
let community_form = CommunityForm { let community_form = CommunityForm {
name: data.name.to_owned(), name: data.name.to_owned(),
@ -394,7 +407,12 @@ impl Perform for Oper<EditCommunity> {
published: None, published: None,
}; };
let updated_community = match Community::update(&conn, data.edit_id, &community_form) { let edit_id = data.edit_id;
let updated_community = match blocking(pool, move |conn| {
Community::update(conn, edit_id, &community_form)
})
.await?
{
Ok(community) => community, Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_update_community").into()), Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
}; };
@ -412,24 +430,36 @@ impl Perform for Oper<EditCommunity> {
reason: data.reason.to_owned(), reason: data.reason.to_owned(),
expires, expires,
}; };
ModRemoveCommunity::create(&conn, &form)?; blocking(pool, move |conn| ModRemoveCommunity::create(conn, &form)).await??;
} }
if let Some(deleted) = data.deleted.to_owned() { if let Some(deleted) = data.deleted.to_owned() {
if deleted { if deleted {
updated_community.send_delete(&user, &conn)?; updated_community
.send_delete(&user, &self.client, pool)
.await?;
} else { } else {
updated_community.send_undo_delete(&user, &conn)?; updated_community
.send_undo_delete(&user, &self.client, pool)
.await?;
} }
} else if let Some(removed) = data.removed.to_owned() { } else if let Some(removed) = data.removed.to_owned() {
if removed { if removed {
updated_community.send_remove(&user, &conn)?; updated_community
.send_remove(&user, &self.client, pool)
.await?;
} else { } else {
updated_community.send_undo_remove(&user, &conn)?; updated_community
.send_undo_remove(&user, &self.client, pool)
.await?;
} }
} }
let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?; let edit_id = data.edit_id;
let community_view = blocking(pool, move |conn| {
CommunityView::read(conn, edit_id, Some(user_id))
})
.await??;
let res = CommunityResponse { let res = CommunityResponse {
community: community_view, community: community_view,
@ -453,14 +483,15 @@ impl Perform for Oper<EditCommunity> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<ListCommunities> { impl Perform for Oper<ListCommunities> {
type Response = ListCommunitiesResponse; type Response = ListCommunitiesResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<ListCommunitiesResponse, Error> { ) -> Result<ListCommunitiesResponse, LemmyError> {
let data: &ListCommunities = &self.data; let data: &ListCommunities = &self.data;
let user_claims: Option<Claims> = match &data.auth { let user_claims: Option<Claims> = match &data.auth {
@ -483,29 +514,33 @@ impl Perform for Oper<ListCommunities> {
let sort = SortType::from_str(&data.sort)?; let sort = SortType::from_str(&data.sort)?;
let conn = pool.get()?; let page = data.page;
let limit = data.limit;
let communities = CommunityQueryBuilder::create(&conn) let communities = blocking(pool, move |conn| {
.sort(&sort) CommunityQueryBuilder::create(conn)
.for_user(user_id) .sort(&sort)
.show_nsfw(show_nsfw) .for_user(user_id)
.page(data.page) .show_nsfw(show_nsfw)
.limit(data.limit) .page(page)
.list()?; .limit(limit)
.list()
})
.await??;
// Return the jwt // Return the jwt
Ok(ListCommunitiesResponse { communities }) Ok(ListCommunitiesResponse { communities })
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<FollowCommunity> { impl Perform for Oper<FollowCommunity> {
type Response = CommunityResponse; type Response = CommunityResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<CommunityResponse, Error> { ) -> Result<CommunityResponse, LemmyError> {
let data: &FollowCommunity = &self.data; let data: &FollowCommunity = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -515,9 +550,8 @@ impl Perform for Oper<FollowCommunity> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?; let community_id = data.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let community = Community::read(&conn, data.community_id)?;
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: data.community_id, community_id: data.community_id,
user_id, user_id,
@ -525,34 +559,44 @@ impl Perform for Oper<FollowCommunity> {
if community.local { if community.local {
if data.follow { if data.follow {
match CommunityFollower::follow(&conn, &community_follower_form) { let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
Ok(user) => user, if blocking(pool, follow).await?.is_err() {
Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), return Err(APIError::err("community_follower_already_exists").into());
}; }
} else { } else {
match CommunityFollower::unfollow(&conn, &community_follower_form) { let unfollow =
Ok(user) => user, move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), if blocking(pool, unfollow).await?.is_err() {
}; return Err(APIError::err("community_follower_already_exists").into());
}
} }
} else { } else {
let user = User_::read(&conn, user_id)?; let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
if data.follow { 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
user.send_follow(&community.actor_id, &conn)?; user
.send_follow(&community.actor_id, &self.client, pool)
.await?;
} else { } else {
user.send_unfollow(&community.actor_id, &conn)?; user
match CommunityFollower::unfollow(&conn, &community_follower_form) { .send_unfollow(&community.actor_id, &self.client, pool)
Ok(user) => user, .await?;
Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), let unfollow =
}; move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
if blocking(pool, unfollow).await?.is_err() {
return Err(APIError::err("community_follower_already_exists").into());
}
} }
// TODO: this needs to return a "pending" state, until Accept is received from the remote server // TODO: this needs to return a "pending" state, until Accept is received from the remote server
} }
let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?; let community_id = data.community_id;
let community_view = blocking(pool, move |conn| {
CommunityView::read(conn, community_id, Some(user_id))
})
.await??;
Ok(CommunityResponse { Ok(CommunityResponse {
community: community_view, community: community_view,
@ -560,14 +604,15 @@ impl Perform for Oper<FollowCommunity> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<GetFollowedCommunities> { impl Perform for Oper<GetFollowedCommunities> {
type Response = GetFollowedCommunitiesResponse; type Response = GetFollowedCommunitiesResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<GetFollowedCommunitiesResponse, Error> { ) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
let data: &GetFollowedCommunities = &self.data; let data: &GetFollowedCommunities = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -577,27 +622,29 @@ impl Perform for Oper<GetFollowedCommunities> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?; let communities = match blocking(pool, move |conn| {
CommunityFollowerView::for_user(conn, user_id)
let communities: Vec<CommunityFollowerView> = })
match CommunityFollowerView::for_user(&conn, user_id) { .await?
Ok(communities) => communities, {
Err(_e) => return Err(APIError::err("system_err_login").into()), Ok(communities) => communities,
}; _ => return Err(APIError::err("system_err_login").into()),
};
// Return the jwt // Return the jwt
Ok(GetFollowedCommunitiesResponse { communities }) Ok(GetFollowedCommunitiesResponse { communities })
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<BanFromCommunity> { impl Perform for Oper<BanFromCommunity> {
type Response = BanFromCommunityResponse; type Response = BanFromCommunityResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<BanFromCommunityResponse, Error> { ) -> Result<BanFromCommunityResponse, LemmyError> {
let data: &BanFromCommunity = &self.data; let data: &BanFromCommunity = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -612,18 +659,16 @@ impl Perform for Oper<BanFromCommunity> {
user_id: data.user_id, user_id: data.user_id,
}; };
let conn = pool.get()?;
if data.ban { if data.ban {
match CommunityUserBan::ban(&conn, &community_user_ban_form) { let ban = move |conn: &'_ _| CommunityUserBan::ban(conn, &community_user_ban_form);
Ok(user) => user, if blocking(pool, ban).await?.is_err() {
Err(_e) => return Err(APIError::err("community_user_already_banned").into()), return Err(APIError::err("community_user_already_banned").into());
}; }
} else { } else {
match CommunityUserBan::unban(&conn, &community_user_ban_form) { let unban = move |conn: &'_ _| CommunityUserBan::unban(conn, &community_user_ban_form);
Ok(user) => user, if blocking(pool, unban).await?.is_err() {
Err(_e) => return Err(APIError::err("community_user_already_banned").into()), return Err(APIError::err("community_user_already_banned").into());
}; }
} }
// Mod tables // Mod tables
@ -640,9 +685,10 @@ impl Perform for Oper<BanFromCommunity> {
banned: Some(data.ban), banned: Some(data.ban),
expires, expires,
}; };
ModBanFromCommunity::create(&conn, &form)?; blocking(pool, move |conn| ModBanFromCommunity::create(conn, &form)).await??;
let user_view = UserView::read(&conn, data.user_id)?; let user_id = data.user_id;
let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
let res = BanFromCommunityResponse { let res = BanFromCommunityResponse {
user: user_view, user: user_view,
@ -662,14 +708,15 @@ impl Perform for Oper<BanFromCommunity> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<AddModToCommunity> { impl Perform for Oper<AddModToCommunity> {
type Response = AddModToCommunityResponse; type Response = AddModToCommunityResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<AddModToCommunityResponse, Error> { ) -> Result<AddModToCommunityResponse, LemmyError> {
let data: &AddModToCommunity = &self.data; let data: &AddModToCommunity = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -684,18 +731,16 @@ impl Perform for Oper<AddModToCommunity> {
user_id: data.user_id, user_id: data.user_id,
}; };
let conn = pool.get()?;
if data.added { if data.added {
match CommunityModerator::join(&conn, &community_moderator_form) { let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
Ok(user) => user, if blocking(pool, join).await?.is_err() {
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), return Err(APIError::err("community_moderator_already_exists").into());
}; }
} else { } else {
match CommunityModerator::leave(&conn, &community_moderator_form) { let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
Ok(user) => user, if blocking(pool, leave).await?.is_err() {
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), return Err(APIError::err("community_moderator_already_exists").into());
}; }
} }
// Mod tables // Mod tables
@ -705,9 +750,13 @@ impl Perform for Oper<AddModToCommunity> {
community_id: data.community_id, community_id: data.community_id,
removed: Some(!data.added), removed: Some(!data.added),
}; };
ModAddCommunity::create(&conn, &form)?; blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??;
let moderators = CommunityModeratorView::for_community(&conn, data.community_id)?; let community_id = data.community_id;
let moderators = blocking(pool, move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
let res = AddModToCommunityResponse { moderators }; let res = AddModToCommunityResponse { moderators };
@ -724,14 +773,15 @@ impl Perform for Oper<AddModToCommunity> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<TransferCommunity> { impl Perform for Oper<TransferCommunity> {
type Response = GetCommunityResponse; type Response = GetCommunityResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<GetCommunityResponse, Error> { ) -> Result<GetCommunityResponse, LemmyError> {
let data: &TransferCommunity = &self.data; let data: &TransferCommunity = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -741,12 +791,14 @@ impl Perform for Oper<TransferCommunity> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?; let community_id = data.community_id;
let read_community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let read_community = Community::read(&conn, data.community_id)?; let site_creator_id =
blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??;
let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
let site_creator_id = Site::read(&conn, 1)?.creator_id;
let mut admins = UserView::admins(&conn)?;
let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap(); let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
let creator_user = admins.remove(creator_index); let creator_user = admins.remove(creator_index);
admins.insert(0, creator_user); admins.insert(0, creator_user);
@ -774,13 +826,18 @@ impl Perform for Oper<TransferCommunity> {
published: None, published: None,
}; };
let _updated_community = match Community::update(&conn, data.community_id, &community_form) { let community_id = data.community_id;
Ok(community) => community, let update = move |conn: &'_ _| Community::update(conn, community_id, &community_form);
Err(_e) => return Err(APIError::err("couldnt_update_community").into()), if blocking(pool, update).await?.is_err() {
return Err(APIError::err("couldnt_update_community").into());
}; };
// You also have to re-do the community_moderator table, reordering it. // You also have to re-do the community_moderator table, reordering it.
let mut community_mods = CommunityModeratorView::for_community(&conn, data.community_id)?; let community_id = data.community_id;
let mut community_mods = blocking(pool, move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
let creator_index = community_mods let creator_index = community_mods
.iter() .iter()
.position(|r| r.user_id == data.user_id) .position(|r| r.user_id == data.user_id)
@ -788,19 +845,23 @@ impl Perform for Oper<TransferCommunity> {
let creator_user = community_mods.remove(creator_index); let creator_user = community_mods.remove(creator_index);
community_mods.insert(0, creator_user); community_mods.insert(0, creator_user);
CommunityModerator::delete_for_community(&conn, data.community_id)?; let community_id = data.community_id;
blocking(pool, move |conn| {
CommunityModerator::delete_for_community(conn, community_id)
})
.await??;
// TODO: this should probably be a bulk operation
for cmod in &community_mods { 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,
user_id: cmod.user_id, user_id: cmod.user_id,
}; };
let _inserted_community_moderator = let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
match CommunityModerator::join(&conn, &community_moderator_form) { if blocking(pool, join).await?.is_err() {
Ok(user) => user, return Err(APIError::err("community_moderator_already_exists").into());
Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), }
};
} }
// Mod tables // Mod tables
@ -810,14 +871,24 @@ impl Perform for Oper<TransferCommunity> {
community_id: data.community_id, community_id: data.community_id,
removed: Some(false), removed: Some(false),
}; };
ModAddCommunity::create(&conn, &form)?; blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??;
let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) { let community_id = data.community_id;
let community_view = match blocking(pool, move |conn| {
CommunityView::read(conn, community_id, Some(user_id))
})
.await?
{
Ok(community) => community, 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 = match CommunityModeratorView::for_community(&conn, data.community_id) { let community_id = data.community_id;
let moderators = match blocking(pool, move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await?
{
Ok(moderators) => moderators, Ok(moderators) => moderators,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()), Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}; };

View file

@ -1,12 +1,10 @@
use crate::{ use crate::{
db::{community::*, community_view::*, moderator::*, site::*, user::*, user_view::*}, db::{community::*, community_view::*, moderator::*, site::*, user::*, user_view::*},
websocket::WebsocketInfo, websocket::WebsocketInfo,
DbPool,
LemmyError,
}; };
use diesel::{ use actix_web::client::Client;
r2d2::{ConnectionManager, Pool},
PgConnection,
};
use failure::Error;
pub mod comment; pub mod comment;
pub mod community; pub mod community;
@ -30,20 +28,22 @@ impl APIError {
pub struct Oper<T> { pub struct Oper<T> {
data: T, data: T,
client: Client,
} }
impl<Data> Oper<Data> { impl<Data> Oper<Data> {
pub fn new(data: Data) -> Oper<Data> { pub fn new(data: Data, client: Client) -> Oper<Data> {
Oper { data } Oper { data, client }
} }
} }
#[async_trait::async_trait(?Send)]
pub trait Perform { pub trait Perform {
type Response: serde::ser::Serialize + Send; type Response: serde::ser::Serialize + Send;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<Self::Response, Error>; ) -> Result<Self::Response, LemmyError>;
} }

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
api::{APIError, Oper, Perform}, api::{APIError, Oper, Perform},
apub::{ApubLikeableType, ApubObjectType}, apub::{ApubLikeableType, ApubObjectType},
blocking,
db::{ db::{
comment_view::*, comment_view::*,
community_view::*, community_view::*,
@ -26,12 +27,9 @@ use crate::{
UserOperation, UserOperation,
WebsocketInfo, WebsocketInfo,
}, },
DbPool,
LemmyError,
}; };
use diesel::{
r2d2::{ConnectionManager, Pool},
PgConnection,
};
use failure::Error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr; use std::str::FromStr;
@ -112,14 +110,15 @@ pub struct SavePost {
auth: String, auth: String,
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<CreatePost> { impl Perform for Oper<CreatePost> {
type Response = PostResponse; type Response = PostResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, Error> { ) -> Result<PostResponse, LemmyError> {
let data: &CreatePost = &self.data; let data: &CreatePost = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -139,22 +138,23 @@ impl Perform for Oper<CreatePost> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?;
// Check for a community ban // Check for a community ban
if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() { let community_id = data.community_id;
let is_banned =
move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
if blocking(pool, is_banned).await? {
return Err(APIError::err("community_ban").into()); return Err(APIError::err("community_ban").into());
} }
// Check for a site ban // Check for a site ban
let user = User_::read(&conn, user_id)?; let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
if user.banned { if user.banned {
return Err(APIError::err("site_ban").into()); return Err(APIError::err("site_ban").into());
} }
// Fetch Iframely and pictrs cached image // Fetch Iframely and pictrs cached image
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
fetch_iframely_and_pictrs_data(data.url.to_owned()); fetch_iframely_and_pictrs_data(&self.client, data.url.to_owned()).await;
let post_form = PostForm { let post_form = PostForm {
name: data.name.to_owned(), name: data.name.to_owned(),
@ -177,7 +177,7 @@ impl Perform for Oper<CreatePost> {
published: None, published: None,
}; };
let inserted_post = match Post::create(&conn, &post_form) { let inserted_post = match blocking(pool, move |conn| Post::create(conn, &post_form)).await? {
Ok(post) => post, Ok(post) => post,
Err(e) => { Err(e) => {
let err_type = if e.to_string() == "value too long for type character varying(200)" { let err_type = if e.to_string() == "value too long for type character varying(200)" {
@ -190,12 +190,14 @@ impl Perform for Oper<CreatePost> {
} }
}; };
let updated_post = match Post::update_ap_id(&conn, inserted_post.id) { let inserted_post_id = inserted_post.id;
Ok(post) => post, let updated_post =
Err(_e) => return Err(APIError::err("couldnt_create_post").into()), match blocking(pool, move |conn| Post::update_ap_id(conn, inserted_post_id)).await? {
}; Ok(post) => post,
Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
};
updated_post.send_create(&user, &conn)?; updated_post.send_create(&user, &self.client, pool).await?;
// They like their own post by default // They like their own post by default
let like_form = PostLikeForm { let like_form = PostLikeForm {
@ -204,15 +206,20 @@ impl Perform for Oper<CreatePost> {
score: 1, score: 1,
}; };
let _inserted_like = match PostLike::like(&conn, &like_form) { let like = move |conn: &'_ _| PostLike::like(conn, &like_form);
Ok(like) => like, if blocking(pool, like).await?.is_err() {
Err(_e) => return Err(APIError::err("couldnt_like_post").into()), return Err(APIError::err("couldnt_like_post").into());
}; }
updated_post.send_like(&user, &conn)?; updated_post.send_like(&user, &self.client, pool).await?;
// Refetch the view // Refetch the view
let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) { let inserted_post_id = inserted_post.id;
let post_view = match blocking(pool, move |conn| {
PostView::read(conn, inserted_post_id, Some(user_id))
})
.await?
{
Ok(post) => post, Ok(post) => post,
Err(_e) => return Err(APIError::err("couldnt_find_post").into()), Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
}; };
@ -231,14 +238,15 @@ impl Perform for Oper<CreatePost> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<GetPost> { impl Perform for Oper<GetPost> {
type Response = GetPostResponse; type Response = GetPostResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<GetPostResponse, Error> { ) -> Result<GetPostResponse, LemmyError> {
let data: &GetPost = &self.data; let data: &GetPost = &self.data;
let user_id: Option<i32> = match &data.auth { let user_id: Option<i32> = match &data.auth {
@ -252,25 +260,38 @@ impl Perform for Oper<GetPost> {
None => None, None => None,
}; };
let conn = pool.get()?; let id = data.id;
let post_view = match blocking(pool, move |conn| PostView::read(conn, id, user_id)).await? {
let post_view = match PostView::read(&conn, data.id, user_id) {
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 comments = CommentQueryBuilder::create(&conn) let id = data.id;
.for_post_id(data.id) let comments = blocking(pool, move |conn| {
.my_user_id(user_id) CommentQueryBuilder::create(conn)
.limit(9999) .for_post_id(id)
.list()?; .my_user_id(user_id)
.limit(9999)
.list()
})
.await??;
let community = CommunityView::read(&conn, post_view.community_id, user_id)?; let community_id = post_view.community_id;
let community = blocking(pool, move |conn| {
CommunityView::read(conn, community_id, user_id)
})
.await??;
let moderators = CommunityModeratorView::for_community(&conn, post_view.community_id)?; let community_id = post_view.community_id;
let moderators = blocking(pool, move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
let site_creator_id = Site::read(&conn, 1)?.creator_id; let site_creator_id =
let mut admins = UserView::admins(&conn)?; blocking(pool, move |conn| Site::read(conn, 1).map(|s| s.creator_id)).await??;
let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap(); let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
let creator_user = admins.remove(creator_index); let creator_user = admins.remove(creator_index);
admins.insert(0, creator_user); admins.insert(0, creator_user);
@ -305,14 +326,15 @@ impl Perform for Oper<GetPost> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<GetPosts> { impl Perform for Oper<GetPosts> {
type Response = GetPostsResponse; type Response = GetPostsResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<GetPostsResponse, Error> { ) -> Result<GetPostsResponse, LemmyError> {
let data: &GetPosts = &self.data; let data: &GetPosts = &self.data;
let user_claims: Option<Claims> = match &data.auth { let user_claims: Option<Claims> = match &data.auth {
@ -336,17 +358,21 @@ impl Perform for Oper<GetPosts> {
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)?;
let conn = pool.get()?; let page = data.page;
let limit = data.limit;
let posts = match PostQueryBuilder::create(&conn) let community_id = data.community_id;
.listing_type(type_) let posts = match blocking(pool, move |conn| {
.sort(&sort) PostQueryBuilder::create(conn)
.show_nsfw(show_nsfw) .listing_type(type_)
.for_community_id(data.community_id) .sort(&sort)
.my_user_id(user_id) .show_nsfw(show_nsfw)
.page(data.page) .for_community_id(community_id)
.limit(data.limit) .my_user_id(user_id)
.list() .page(page)
.limit(limit)
.list()
})
.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()),
@ -370,14 +396,15 @@ impl Perform for Oper<GetPosts> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<CreatePostLike> { impl Perform for Oper<CreatePostLike> {
type Response = PostResponse; type Response = PostResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, Error> { ) -> Result<PostResponse, LemmyError> {
let data: &CreatePostLike = &self.data; let data: &CreatePostLike = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -387,24 +414,27 @@ impl Perform for Oper<CreatePostLike> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?;
// Don't do a downvote if site has downvotes disabled // Don't do a downvote if site has downvotes disabled
if data.score == -1 { if data.score == -1 {
let site = SiteView::read(&conn)?; let site = blocking(pool, move |conn| SiteView::read(conn)).await??;
if !site.enable_downvotes { if !site.enable_downvotes {
return Err(APIError::err("downvotes_disabled").into()); return Err(APIError::err("downvotes_disabled").into());
} }
} }
// Check for a community ban // Check for a community ban
let post = Post::read(&conn, data.post_id)?; let post_id = data.post_id;
if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let is_banned =
move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
if blocking(pool, is_banned).await? {
return Err(APIError::err("community_ban").into()); return Err(APIError::err("community_ban").into());
} }
// Check for a site ban // Check for a site ban
let user = User_::read(&conn, user_id)?; let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
if user.banned { if user.banned {
return Err(APIError::err("site_ban").into()); return Err(APIError::err("site_ban").into());
} }
@ -416,26 +446,33 @@ impl Perform for Oper<CreatePostLike> {
}; };
// Remove any likes first // Remove any likes first
PostLike::remove(&conn, &like_form)?; let like_form2 = like_form.clone();
blocking(pool, move |conn| PostLike::remove(conn, &like_form2)).await??;
// Only add the like if the score isnt 0 // Only add the like if the score isnt 0
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
if do_add { if do_add {
let _inserted_like = match PostLike::like(&conn, &like_form) { let like_form2 = like_form.clone();
Ok(like) => like, let like = move |conn: &'_ _| PostLike::like(conn, &like_form2);
Err(_e) => return Err(APIError::err("couldnt_like_post").into()), if blocking(pool, like).await?.is_err() {
}; return Err(APIError::err("couldnt_like_post").into());
}
if like_form.score == 1 { if like_form.score == 1 {
post.send_like(&user, &conn)?; post.send_like(&user, &self.client, pool).await?;
} else if like_form.score == -1 { } else if like_form.score == -1 {
post.send_dislike(&user, &conn)?; post.send_dislike(&user, &self.client, pool).await?;
} }
} else { } else {
post.send_undo_like(&user, &conn)?; post.send_undo_like(&user, &self.client, pool).await?;
} }
let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) { let post_id = data.post_id;
let post_view = match blocking(pool, move |conn| {
PostView::read(conn, post_id, Some(user_id))
})
.await?
{
Ok(post) => post, Ok(post) => post,
Err(_e) => return Err(APIError::err("couldnt_find_post").into()), Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
}; };
@ -454,14 +491,15 @@ impl Perform for Oper<CreatePostLike> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<EditPost> { impl Perform for Oper<EditPost> {
type Response = PostResponse; type Response = PostResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, Error> { ) -> Result<PostResponse, LemmyError> {
let data: &EditPost = &self.data; let data: &EditPost = &self.data;
if let Err(slurs) = slur_check(&data.name) { if let Err(slurs) = slur_check(&data.name) {
@ -481,37 +519,46 @@ impl Perform for Oper<EditPost> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?;
// Verify its the creator or a mod or admin // Verify its the creator or a mod or admin
let community_id = data.community_id;
let mut editors: Vec<i32> = vec![data.creator_id]; let mut editors: Vec<i32> = vec![data.creator_id];
editors.append( editors.append(
&mut CommunityModeratorView::for_community(&conn, data.community_id)? &mut blocking(pool, move |conn| {
.into_iter() CommunityModeratorView::for_community(conn, community_id)
.map(|m| m.user_id) .map(|v| v.into_iter().map(|m| m.user_id).collect())
.collect(), })
.await??,
);
editors.append(
&mut blocking(pool, move |conn| {
UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())
})
.await??,
); );
editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
if !editors.contains(&user_id) { if !editors.contains(&user_id) {
return Err(APIError::err("no_post_edit_allowed").into()); return Err(APIError::err("no_post_edit_allowed").into());
} }
// Check for a community ban // Check for a community ban
if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() { let community_id = data.community_id;
let is_banned =
move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
if blocking(pool, is_banned).await? {
return Err(APIError::err("community_ban").into()); return Err(APIError::err("community_ban").into());
} }
// Check for a site ban // Check for a site ban
let user = User_::read(&conn, user_id)?; let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
if user.banned { if user.banned {
return Err(APIError::err("site_ban").into()); return Err(APIError::err("site_ban").into());
} }
// Fetch Iframely and Pictrs cached image // Fetch Iframely and Pictrs cached image
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
fetch_iframely_and_pictrs_data(data.url.to_owned()); fetch_iframely_and_pictrs_data(&self.client, data.url.to_owned()).await;
let read_post = Post::read(&conn, data.edit_id)?; let edit_id = data.edit_id;
let read_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??;
let post_form = PostForm { let post_form = PostForm {
name: data.name.to_owned(), name: data.name.to_owned(),
@ -534,7 +581,9 @@ impl Perform for Oper<EditPost> {
published: None, published: None,
}; };
let updated_post = match Post::update(&conn, data.edit_id, &post_form) { let edit_id = data.edit_id;
let res = blocking(pool, move |conn| Post::update(conn, edit_id, &post_form)).await?;
let updated_post: Post = match res {
Ok(post) => post, Ok(post) => post,
Err(e) => { Err(e) => {
let err_type = if e.to_string() == "value too long for type character varying(200)" { let err_type = if e.to_string() == "value too long for type character varying(200)" {
@ -555,7 +604,7 @@ impl Perform for Oper<EditPost> {
removed: Some(removed), removed: Some(removed),
reason: data.reason.to_owned(), reason: data.reason.to_owned(),
}; };
ModRemovePost::create(&conn, &form)?; blocking(pool, move |conn| ModRemovePost::create(conn, &form)).await??;
} }
if let Some(locked) = data.locked.to_owned() { if let Some(locked) = data.locked.to_owned() {
@ -564,7 +613,7 @@ impl Perform for Oper<EditPost> {
post_id: data.edit_id, post_id: data.edit_id,
locked: Some(locked), locked: Some(locked),
}; };
ModLockPost::create(&conn, &form)?; blocking(pool, move |conn| ModLockPost::create(conn, &form)).await??;
} }
if let Some(stickied) = data.stickied.to_owned() { if let Some(stickied) = data.stickied.to_owned() {
@ -573,26 +622,34 @@ impl Perform for Oper<EditPost> {
post_id: data.edit_id, post_id: data.edit_id,
stickied: Some(stickied), stickied: Some(stickied),
}; };
ModStickyPost::create(&conn, &form)?; blocking(pool, move |conn| ModStickyPost::create(conn, &form)).await??;
} }
if let Some(deleted) = data.deleted.to_owned() { if let Some(deleted) = data.deleted.to_owned() {
if deleted { if deleted {
updated_post.send_delete(&user, &conn)?; updated_post.send_delete(&user, &self.client, pool).await?;
} else { } else {
updated_post.send_undo_delete(&user, &conn)?; updated_post
.send_undo_delete(&user, &self.client, pool)
.await?;
} }
} else if let Some(removed) = data.removed.to_owned() { } else if let Some(removed) = data.removed.to_owned() {
if removed { if removed {
updated_post.send_remove(&user, &conn)?; updated_post.send_remove(&user, &self.client, pool).await?;
} else { } else {
updated_post.send_undo_remove(&user, &conn)?; updated_post
.send_undo_remove(&user, &self.client, pool)
.await?;
} }
} else { } else {
updated_post.send_update(&user, &conn)?; updated_post.send_update(&user, &self.client, pool).await?;
} }
let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?; let edit_id = data.edit_id;
let post_view = blocking(pool, move |conn| {
PostView::read(conn, edit_id, Some(user_id))
})
.await??;
let res = PostResponse { post: post_view }; let res = PostResponse { post: post_view };
@ -608,14 +665,15 @@ impl Perform for Oper<EditPost> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<SavePost> { impl Perform for Oper<SavePost> {
type Response = PostResponse; type Response = PostResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, Error> { ) -> Result<PostResponse, LemmyError> {
let data: &SavePost = &self.data; let data: &SavePost = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -630,21 +688,23 @@ impl Perform for Oper<SavePost> {
user_id, user_id,
}; };
let conn = pool.get()?;
if data.save { if data.save {
match PostSaved::save(&conn, &post_saved_form) { let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form);
Ok(post) => post, if blocking(pool, save).await?.is_err() {
Err(_e) => return Err(APIError::err("couldnt_save_post").into()), return Err(APIError::err("couldnt_save_post").into());
}; }
} else { } else {
match PostSaved::unsave(&conn, &post_saved_form) { let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form);
Ok(post) => post, if blocking(pool, unsave).await?.is_err() {
Err(_e) => return Err(APIError::err("couldnt_save_post").into()), return Err(APIError::err("couldnt_save_post").into());
}; }
} }
let post_view = PostView::read(&conn, data.post_id, Some(user_id))?; let post_id = data.post_id;
let post_view = blocking(pool, move |conn| {
PostView::read(conn, post_id, Some(user_id))
})
.await??;
Ok(PostResponse { post: post_view }) Ok(PostResponse { post: post_view })
} }

View file

@ -2,6 +2,7 @@ use super::user::Register;
use crate::{ use crate::{
api::{APIError, Oper, Perform}, api::{APIError, Oper, Perform},
apub::fetcher::search_by_apub_id, apub::fetcher::search_by_apub_id,
blocking,
db::{ db::{
category::*, category::*,
comment_view::*, comment_view::*,
@ -22,12 +23,9 @@ use crate::{
slur_check, slur_check,
slurs_vec_to_str, slurs_vec_to_str,
websocket::{server::SendAllMessage, UserOperation, WebsocketInfo}, websocket::{server::SendAllMessage, UserOperation, WebsocketInfo},
DbPool,
LemmyError,
}; };
use diesel::{
r2d2::{ConnectionManager, Pool},
PgConnection,
};
use failure::Error;
use log::{debug, info}; use log::{debug, info};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr; use std::str::FromStr;
@ -139,87 +137,79 @@ pub struct SaveSiteConfig {
auth: String, auth: String,
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<ListCategories> { impl Perform for Oper<ListCategories> {
type Response = ListCategoriesResponse; type Response = ListCategoriesResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<ListCategoriesResponse, Error> { ) -> Result<ListCategoriesResponse, LemmyError> {
let _data: &ListCategories = &self.data; let _data: &ListCategories = &self.data;
let conn = pool.get()?; let categories = blocking(pool, move |conn| Category::list_all(conn)).await??;
let categories: Vec<Category> = Category::list_all(&conn)?;
// Return the jwt // Return the jwt
Ok(ListCategoriesResponse { categories }) Ok(ListCategoriesResponse { categories })
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<GetModlog> { impl Perform for Oper<GetModlog> {
type Response = GetModlogResponse; type Response = GetModlogResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<GetModlogResponse, Error> { ) -> Result<GetModlogResponse, LemmyError> {
let data: &GetModlog = &self.data; let data: &GetModlog = &self.data;
let conn = pool.get()?; let community_id = data.community_id;
let mod_user_id = data.mod_user_id;
let page = data.page;
let limit = data.limit;
let removed_posts = blocking(pool, move |conn| {
ModRemovePostView::list(conn, community_id, mod_user_id, page, limit)
})
.await??;
let removed_posts = ModRemovePostView::list( let locked_posts = blocking(pool, move |conn| {
&conn, ModLockPostView::list(conn, community_id, mod_user_id, page, limit)
data.community_id, })
data.mod_user_id, .await??;
data.page,
data.limit, let stickied_posts = blocking(pool, move |conn| {
)?; ModStickyPostView::list(conn, community_id, mod_user_id, page, limit)
let locked_posts = ModLockPostView::list( })
&conn, .await??;
data.community_id,
data.mod_user_id, let removed_comments = blocking(pool, move |conn| {
data.page, ModRemoveCommentView::list(conn, community_id, mod_user_id, page, limit)
data.limit, })
)?; .await??;
let stickied_posts = ModStickyPostView::list(
&conn, let banned_from_community = blocking(pool, move |conn| {
data.community_id, ModBanFromCommunityView::list(conn, community_id, mod_user_id, page, limit)
data.mod_user_id, })
data.page, .await??;
data.limit,
)?; let added_to_community = blocking(pool, move |conn| {
let removed_comments = ModRemoveCommentView::list( ModAddCommunityView::list(conn, community_id, mod_user_id, page, limit)
&conn, })
data.community_id, .await??;
data.mod_user_id,
data.page,
data.limit,
)?;
let banned_from_community = ModBanFromCommunityView::list(
&conn,
data.community_id,
data.mod_user_id,
data.page,
data.limit,
)?;
let added_to_community = ModAddCommunityView::list(
&conn,
data.community_id,
data.mod_user_id,
data.page,
data.limit,
)?;
// These arrays are only for the full modlog, when a community isn't given // These arrays are only for the full modlog, when a community isn't given
let (removed_communities, banned, added) = if data.community_id.is_none() { let (removed_communities, banned, added) = if data.community_id.is_none() {
( blocking(pool, move |conn| {
ModRemoveCommunityView::list(&conn, data.mod_user_id, data.page, data.limit)?, Ok((
ModBanView::list(&conn, data.mod_user_id, data.page, data.limit)?, ModRemoveCommunityView::list(conn, mod_user_id, page, limit)?,
ModAddView::list(&conn, data.mod_user_id, data.page, data.limit)?, ModBanView::list(conn, mod_user_id, page, limit)?,
) ModAddView::list(conn, mod_user_id, page, limit)?,
)) as Result<_, LemmyError>
})
.await??
} else { } else {
(Vec::new(), Vec::new(), Vec::new()) (Vec::new(), Vec::new(), Vec::new())
}; };
@ -239,14 +229,15 @@ impl Perform for Oper<GetModlog> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<CreateSite> { impl Perform for Oper<CreateSite> {
type Response = SiteResponse; type Response = SiteResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<SiteResponse, Error> { ) -> Result<SiteResponse, LemmyError> {
let data: &CreateSite = &self.data; let data: &CreateSite = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -266,10 +257,9 @@ impl Perform for Oper<CreateSite> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?;
// Make sure user is an admin // Make sure user is an admin
if !UserView::read(&conn, user_id)?.admin { let user = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
if !user.admin {
return Err(APIError::err("not_an_admin").into()); return Err(APIError::err("not_an_admin").into());
} }
@ -283,24 +273,25 @@ impl Perform for Oper<CreateSite> {
updated: None, updated: None,
}; };
match Site::create(&conn, &site_form) { let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
Ok(site) => site, if blocking(pool, create_site).await?.is_err() {
Err(_e) => return Err(APIError::err("site_already_exists").into()), return Err(APIError::err("site_already_exists").into());
}; }
let site_view = SiteView::read(&conn)?; let site_view = blocking(pool, move |conn| SiteView::read(conn)).await??;
Ok(SiteResponse { site: site_view }) Ok(SiteResponse { site: site_view })
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<EditSite> { impl Perform for Oper<EditSite> {
type Response = SiteResponse; type Response = SiteResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<SiteResponse, Error> { ) -> Result<SiteResponse, LemmyError> {
let data: &EditSite = &self.data; let data: &EditSite = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -320,14 +311,13 @@ impl Perform for Oper<EditSite> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?;
// Make sure user is an admin // Make sure user is an admin
if !UserView::read(&conn, user_id)?.admin { let user = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
if !user.admin {
return Err(APIError::err("not_an_admin").into()); return Err(APIError::err("not_an_admin").into());
} }
let found_site = Site::read(&conn, 1)?; let found_site = blocking(pool, move |conn| Site::read(conn, 1)).await??;
let site_form = SiteForm { let site_form = SiteForm {
name: data.name.to_owned(), name: data.name.to_owned(),
@ -339,12 +329,12 @@ impl Perform for Oper<EditSite> {
enable_nsfw: data.enable_nsfw, enable_nsfw: data.enable_nsfw,
}; };
match Site::update(&conn, 1, &site_form) { let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
Ok(site) => site, if blocking(pool, update_site).await?.is_err() {
Err(_e) => return Err(APIError::err("couldnt_update_site").into()), return Err(APIError::err("couldnt_update_site").into());
}; }
let site_view = SiteView::read(&conn)?; let site_view = blocking(pool, move |conn| SiteView::read(conn)).await??;
let res = SiteResponse { site: site_view }; let res = SiteResponse { site: site_view };
@ -360,21 +350,21 @@ impl Perform for Oper<EditSite> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<GetSite> { impl Perform for Oper<GetSite> {
type Response = GetSiteResponse; type Response = GetSiteResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
websocket_info: Option<WebsocketInfo>, websocket_info: Option<WebsocketInfo>,
) -> Result<GetSiteResponse, Error> { ) -> Result<GetSiteResponse, LemmyError> {
let _data: &GetSite = &self.data; let _data: &GetSite = &self.data;
let conn = pool.get()?;
// TODO refactor this a little // TODO refactor this a little
let site_view = if let Ok(_site) = Site::read(&conn, 1) { let res = blocking(pool, move |conn| Site::read(conn, 1)).await?;
Some(SiteView::read(&conn)?) let site_view = if res.is_ok() {
Some(blocking(pool, move |conn| SiteView::read(conn)).await??)
} else if let Some(setup) = Settings::get().setup.as_ref() { } else 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(),
@ -384,7 +374,9 @@ impl Perform for Oper<GetSite> {
admin: true, admin: true,
show_nsfw: true, show_nsfw: true,
}; };
let login_response = Oper::new(register).perform(pool.clone(), websocket_info.clone())?; let login_response = Oper::new(register, self.client.clone())
.perform(pool, websocket_info.clone())
.await?;
info!("Admin {} created", setup.admin_username); info!("Admin {} created", setup.admin_username);
let create_site = CreateSite { let create_site = CreateSite {
@ -395,14 +387,16 @@ impl Perform for Oper<GetSite> {
enable_nsfw: true, enable_nsfw: true,
auth: login_response.jwt, auth: login_response.jwt,
}; };
Oper::new(create_site).perform(pool, websocket_info.clone())?; Oper::new(create_site, self.client.clone())
.perform(pool, websocket_info.clone())
.await?;
info!("Site {} created", setup.site_name); info!("Site {} created", setup.site_name);
Some(SiteView::read(&conn)?) Some(blocking(pool, move |conn| SiteView::read(conn)).await??)
} else { } else {
None None
}; };
let mut admins = UserView::admins(&conn)?; let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
// Make sure the site creator is the top admin // 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() {
@ -415,7 +409,7 @@ impl Perform for Oper<GetSite> {
} }
} }
let banned = UserView::banned(&conn)?; let banned = blocking(pool, move |conn| UserView::banned(conn)).await??;
let online = if let Some(_ws) = websocket_info { let online = if let Some(_ws) = websocket_info {
// TODO // TODO
@ -437,21 +431,20 @@ impl Perform for Oper<GetSite> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<Search> { impl Perform for Oper<Search> {
type Response = SearchResponse; type Response = SearchResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<SearchResponse, Error> { ) -> Result<SearchResponse, LemmyError> {
let data: &Search = &self.data; let data: &Search = &self.data;
dbg!(&data); dbg!(&data);
let conn = pool.get()?; match search_by_apub_id(&data.q, &self.client, pool).await {
match search_by_apub_id(&data.q, &conn) {
Ok(r) => return Ok(r), Ok(r) => return Ok(r),
Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e), Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
} }
@ -467,7 +460,6 @@ impl Perform for Oper<Search> {
None => None, None => None,
}; };
let sort = SortType::from_str(&data.sort)?;
let type_ = SearchType::from_str(&data.type_)?; let type_ = SearchType::from_str(&data.type_)?;
let mut posts = Vec::new(); let mut posts = Vec::new();
@ -477,85 +469,126 @@ impl Perform for Oper<Search> {
// TODO no clean / non-nsfw searching rn // TODO no clean / non-nsfw searching rn
let q = data.q.to_owned();
let page = data.page;
let limit = data.limit;
let sort = SortType::from_str(&data.sort)?;
let community_id = data.community_id;
match type_ { match type_ {
SearchType::Posts => { SearchType::Posts => {
posts = PostQueryBuilder::create(&conn) posts = blocking(pool, move |conn| {
.sort(&sort) PostQueryBuilder::create(conn)
.show_nsfw(true) .sort(&sort)
.for_community_id(data.community_id) .show_nsfw(true)
.search_term(data.q.to_owned()) .for_community_id(community_id)
.my_user_id(user_id) .search_term(q)
.page(data.page) .my_user_id(user_id)
.limit(data.limit) .page(page)
.list()?; .limit(limit)
.list()
})
.await??;
} }
SearchType::Comments => { SearchType::Comments => {
comments = CommentQueryBuilder::create(&conn) comments = blocking(pool, move |conn| {
.sort(&sort) CommentQueryBuilder::create(&conn)
.search_term(data.q.to_owned()) .sort(&sort)
.my_user_id(user_id) .search_term(q)
.page(data.page) .my_user_id(user_id)
.limit(data.limit) .page(page)
.list()?; .limit(limit)
.list()
})
.await??;
} }
SearchType::Communities => { SearchType::Communities => {
communities = CommunityQueryBuilder::create(&conn) communities = blocking(pool, move |conn| {
.sort(&sort) CommunityQueryBuilder::create(conn)
.search_term(data.q.to_owned()) .sort(&sort)
.page(data.page) .search_term(q)
.limit(data.limit) .page(page)
.list()?; .limit(limit)
.list()
})
.await??;
} }
SearchType::Users => { SearchType::Users => {
users = UserQueryBuilder::create(&conn) users = blocking(pool, move |conn| {
.sort(&sort) UserQueryBuilder::create(conn)
.search_term(data.q.to_owned()) .sort(&sort)
.page(data.page) .search_term(q)
.limit(data.limit) .page(page)
.list()?; .limit(limit)
.list()
})
.await??;
} }
SearchType::All => { SearchType::All => {
posts = PostQueryBuilder::create(&conn) posts = blocking(pool, move |conn| {
.sort(&sort) PostQueryBuilder::create(conn)
.show_nsfw(true) .sort(&sort)
.for_community_id(data.community_id) .show_nsfw(true)
.search_term(data.q.to_owned()) .for_community_id(community_id)
.my_user_id(user_id) .search_term(q)
.page(data.page) .my_user_id(user_id)
.limit(data.limit) .page(page)
.list()?; .limit(limit)
.list()
})
.await??;
comments = CommentQueryBuilder::create(&conn) let q = data.q.to_owned();
.sort(&sort) let sort = SortType::from_str(&data.sort)?;
.search_term(data.q.to_owned())
.my_user_id(user_id)
.page(data.page)
.limit(data.limit)
.list()?;
communities = CommunityQueryBuilder::create(&conn) comments = blocking(pool, move |conn| {
.sort(&sort) CommentQueryBuilder::create(conn)
.search_term(data.q.to_owned()) .sort(&sort)
.page(data.page) .search_term(q)
.limit(data.limit) .my_user_id(user_id)
.list()?; .page(page)
.limit(limit)
.list()
})
.await??;
users = UserQueryBuilder::create(&conn) let q = data.q.to_owned();
.sort(&sort) let sort = SortType::from_str(&data.sort)?;
.search_term(data.q.to_owned())
.page(data.page) communities = blocking(pool, move |conn| {
.limit(data.limit) CommunityQueryBuilder::create(conn)
.list()?; .sort(&sort)
.search_term(q)
.page(page)
.limit(limit)
.list()
})
.await??;
let q = data.q.to_owned();
let sort = SortType::from_str(&data.sort)?;
users = blocking(pool, move |conn| {
UserQueryBuilder::create(conn)
.sort(&sort)
.search_term(q)
.page(page)
.limit(limit)
.list()
})
.await??;
} }
SearchType::Url => { SearchType::Url => {
posts = PostQueryBuilder::create(&conn) posts = blocking(pool, move |conn| {
.sort(&sort) PostQueryBuilder::create(conn)
.show_nsfw(true) .sort(&sort)
.for_community_id(data.community_id) .show_nsfw(true)
.url_search(data.q.to_owned()) .for_community_id(community_id)
.page(data.page) .url_search(q)
.limit(data.limit) .page(page)
.list()?; .limit(limit)
.list()
})
.await??;
} }
}; };
@ -570,14 +603,15 @@ impl Perform for Oper<Search> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<TransferSite> { impl Perform for Oper<TransferSite> {
type Response = GetSiteResponse; type Response = GetSiteResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<GetSiteResponse, Error> { ) -> Result<GetSiteResponse, LemmyError> {
let data: &TransferSite = &self.data; let data: &TransferSite = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -587,9 +621,7 @@ impl Perform for Oper<TransferSite> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?; let read_site = blocking(pool, move |conn| Site::read(conn, 1)).await??;
let read_site = Site::read(&conn, 1)?;
// Make sure user is the creator // Make sure user is the creator
if read_site.creator_id != user_id { if read_site.creator_id != user_id {
@ -606,9 +638,9 @@ impl Perform for Oper<TransferSite> {
enable_nsfw: read_site.enable_nsfw, enable_nsfw: read_site.enable_nsfw,
}; };
match Site::update(&conn, 1, &site_form) { let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
Ok(site) => site, if blocking(pool, update_site).await?.is_err() {
Err(_e) => return Err(APIError::err("couldnt_update_site").into()), return Err(APIError::err("couldnt_update_site").into());
}; };
// Mod tables // Mod tables
@ -618,11 +650,11 @@ impl Perform for Oper<TransferSite> {
removed: Some(false), removed: Some(false),
}; };
ModAdd::create(&conn, &form)?; blocking(pool, move |conn| ModAdd::create(conn, &form)).await??;
let site_view = SiteView::read(&conn)?; let site_view = blocking(pool, move |conn| SiteView::read(conn)).await??;
let mut admins = UserView::admins(&conn)?; let mut admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
let creator_index = admins let creator_index = admins
.iter() .iter()
.position(|r| r.id == site_view.creator_id) .position(|r| r.id == site_view.creator_id)
@ -630,7 +662,7 @@ impl Perform for Oper<TransferSite> {
let creator_user = admins.remove(creator_index); let creator_user = admins.remove(creator_index);
admins.insert(0, creator_user); admins.insert(0, creator_user);
let banned = UserView::banned(&conn)?; let banned = blocking(pool, move |conn| UserView::banned(conn)).await??;
Ok(GetSiteResponse { Ok(GetSiteResponse {
site: Some(site_view), site: Some(site_view),
@ -641,14 +673,15 @@ impl Perform for Oper<TransferSite> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<GetSiteConfig> { impl Perform for Oper<GetSiteConfig> {
type Response = GetSiteConfigResponse; type Response = GetSiteConfigResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<GetSiteConfigResponse, Error> { ) -> Result<GetSiteConfigResponse, LemmyError> {
let data: &GetSiteConfig = &self.data; let data: &GetSiteConfig = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -658,10 +691,8 @@ impl Perform for Oper<GetSiteConfig> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?;
// Only let admins read this // Only let admins read this
let admins = UserView::admins(&conn)?; let admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect(); let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
if !admin_ids.contains(&user_id) { if !admin_ids.contains(&user_id) {
@ -674,14 +705,15 @@ impl Perform for Oper<GetSiteConfig> {
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for Oper<SaveSiteConfig> { impl Perform for Oper<SaveSiteConfig> {
type Response = GetSiteConfigResponse; type Response = GetSiteConfigResponse;
fn perform( async fn perform(
&self, &self,
pool: Pool<ConnectionManager<PgConnection>>, pool: &DbPool,
_websocket_info: Option<WebsocketInfo>, _websocket_info: Option<WebsocketInfo>,
) -> Result<GetSiteConfigResponse, Error> { ) -> Result<GetSiteConfigResponse, LemmyError> {
let data: &SaveSiteConfig = &self.data; let data: &SaveSiteConfig = &self.data;
let claims = match Claims::decode(&data.auth) { let claims = match Claims::decode(&data.auth) {
@ -691,10 +723,8 @@ impl Perform for Oper<SaveSiteConfig> {
let user_id = claims.id; let user_id = claims.id;
let conn = pool.get()?;
// Only let admins read this // Only let admins read this
let admins = UserView::admins(&conn)?; let admins = blocking(pool, move |conn| UserView::admins(conn)).await??;
let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect(); let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
if !admin_ids.contains(&user_id) { if !admin_ids.contains(&user_id) {

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,22 @@
use crate::{ use crate::{
apub::{extensions::signatures::sign, is_apub_id_valid, ActorType}, apub::{extensions::signatures::sign, is_apub_id_valid, ActorType},
db::{activity::insert_activity, community::Community, user::User_}, db::{activity::insert_activity, community::Community, user::User_},
request::retry_custom,
DbPool,
LemmyError,
}; };
use activitystreams::{context, object::properties::ObjectProperties, public, Activity, Base}; use activitystreams::{context, object::properties::ObjectProperties, public, Activity, Base};
use diesel::PgConnection; use actix_web::client::Client;
use failure::{Error, _core::fmt::Debug};
use isahc::prelude::*;
use log::debug; use log::debug;
use serde::Serialize; use serde::Serialize;
use std::fmt::Debug;
use url::Url; use url::Url;
pub fn populate_object_props( pub fn populate_object_props(
props: &mut ObjectProperties, props: &mut ObjectProperties,
addressed_ccs: Vec<String>, addressed_ccs: Vec<String>,
object_id: &str, object_id: &str,
) -> Result<(), Error> { ) -> Result<(), LemmyError> {
props props
.set_context_xsd_any_uri(context())? .set_context_xsd_any_uri(context())?
// TODO: the activity needs a seperate id from the object // TODO: the activity needs a seperate id from the object
@ -26,48 +28,61 @@ pub fn populate_object_props(
Ok(()) Ok(())
} }
pub fn send_activity_to_community<A>( pub async fn send_activity_to_community<A>(
creator: &User_, creator: &User_,
conn: &PgConnection,
community: &Community, community: &Community,
to: Vec<String>, to: Vec<String>,
activity: A, activity: A,
) -> Result<(), Error> client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>
where where
A: Activity + Base + Serialize + Debug, A: Activity + Base + Serialize + Debug + Clone + Send + 'static,
{ {
insert_activity(&conn, creator.id, &activity, true)?; insert_activity(creator.id, activity.clone(), true, pool).await?;
// if this is a local community, we need to do an announce from the community instead // if this is a local community, we need to do an announce from the community instead
if community.local { if community.local {
Community::do_announce(activity, &community, creator, conn)?; Community::do_announce(activity, &community, creator, client, pool).await?;
} else { } else {
send_activity(&activity, creator, to)?; send_activity(client, &activity, creator, to).await?;
} }
Ok(()) Ok(())
} }
/// Send an activity to a list of recipients, using the correct headers etc. /// Send an activity to a list of recipients, using the correct headers etc.
pub fn send_activity<A>(activity: &A, actor: &dyn ActorType, to: Vec<String>) -> Result<(), Error> pub async fn send_activity<A>(
client: &Client,
activity: &A,
actor: &dyn ActorType,
to: Vec<String>,
) -> Result<(), LemmyError>
where where
A: Serialize + Debug, A: Serialize,
{ {
let json = serde_json::to_string(&activity)?; let activity = serde_json::to_string(&activity)?;
debug!("Sending activitypub activity {} to {:?}", json, to); debug!("Sending activitypub activity {} to {:?}", activity, to);
for t in to { for t in to {
let to_url = Url::parse(&t)?; let to_url = Url::parse(&t)?;
if !is_apub_id_valid(&to_url) { if !is_apub_id_valid(&to_url) {
debug!("Not sending activity to {} (invalid or blocklisted)", t); debug!("Not sending activity to {} (invalid or blocklisted)", t);
continue; continue;
} }
let request = Request::post(t).header("Host", to_url.domain().unwrap());
let signature = sign(&request, actor)?; let res = retry_custom(|| async {
let res = request let request = client.post(&t).header("Content-Type", "application/json");
.header("Signature", signature)
.header("Content-Type", "application/json") match sign(request, actor, activity.clone()).await {
.body(json.to_owned())? Ok(signed) => Ok(signed.send().await),
.send()?; Err(e) => Err(e),
}
})
.await?;
debug!("Result for activity send: {:?}", res); debug!("Result for activity send: {:?}", res);
} }
Ok(()) Ok(())
} }

View file

@ -16,6 +16,7 @@ use crate::{
FromApub, FromApub,
ToApub, ToApub,
}, },
blocking,
convert_datetime, convert_datetime,
db::{ db::{
comment::{Comment, CommentForm}, comment::{Comment, CommentForm},
@ -26,6 +27,8 @@ use crate::{
}, },
routes::DbPoolParam, routes::DbPoolParam,
scrape_text_for_mentions, scrape_text_for_mentions,
DbPool,
LemmyError,
MentionData, MentionData,
}; };
use activitystreams::{ use activitystreams::{
@ -35,9 +38,7 @@ use activitystreams::{
object::{kind::NoteType, properties::ObjectProperties, Note}, object::{kind::NoteType, properties::ObjectProperties, Note},
}; };
use activitystreams_new::object::Tombstone; use activitystreams_new::object::Tombstone;
use actix_web::{body::Body, web::Path, HttpResponse, Result}; use actix_web::{body::Body, client::Client, web::Path, HttpResponse};
use diesel::PgConnection;
use failure::Error;
use itertools::Itertools; use itertools::Itertools;
use log::debug; use log::debug;
use serde::Deserialize; use serde::Deserialize;
@ -51,32 +52,41 @@ pub struct CommentQuery {
pub async fn get_apub_comment( pub async fn get_apub_comment(
info: Path<CommentQuery>, info: Path<CommentQuery>,
db: DbPoolParam, db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> { ) -> Result<HttpResponse<Body>, LemmyError> {
let id = info.comment_id.parse::<i32>()?; let id = info.comment_id.parse::<i32>()?;
let comment = Comment::read(&&db.get()?, id)?; let comment = blocking(&db, move |conn| Comment::read(conn, id)).await??;
if !comment.deleted { if !comment.deleted {
Ok(create_apub_response(&comment.to_apub(&db.get().unwrap())?)) Ok(create_apub_response(&comment.to_apub(&db).await?))
} else { } else {
Ok(create_apub_tombstone_response(&comment.to_tombstone()?)) Ok(create_apub_tombstone_response(&comment.to_tombstone()?))
} }
} }
#[async_trait::async_trait(?Send)]
impl ToApub for Comment { impl ToApub for Comment {
type Response = Note; type Response = Note;
fn to_apub(&self, conn: &PgConnection) -> Result<Note, Error> { async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
let mut comment = Note::default(); let mut comment = Note::default();
let oprops: &mut ObjectProperties = comment.as_mut(); let oprops: &mut ObjectProperties = comment.as_mut();
let creator = User_::read(&conn, self.creator_id)?;
let post = Post::read(&conn, self.post_id)?; let creator_id = self.creator_id;
let community = Community::read(&conn, post.community_id)?; let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
let post_id = self.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
// Add a vector containing some important info to the "in_reply_to" field // 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]; let mut in_reply_to_vec = vec![post.ap_id];
if let Some(parent_id) = self.parent_id { if let Some(parent_id) = self.parent_id {
let parent_comment = Comment::read(&conn, parent_id)?; let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??;
in_reply_to_vec.push(parent_comment.ap_id); in_reply_to_vec.push(parent_comment.ap_id);
} }
@ -97,7 +107,7 @@ impl ToApub for Comment {
Ok(comment) Ok(comment)
} }
fn to_tombstone(&self) -> Result<Tombstone, Error> { fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
create_tombstone( create_tombstone(
self.deleted, self.deleted,
&self.ap_id, &self.ap_id,
@ -107,27 +117,34 @@ impl ToApub for Comment {
} }
} }
#[async_trait::async_trait(?Send)]
impl FromApub for CommentForm { impl FromApub for CommentForm {
type ApubType = Note; type ApubType = Note;
/// Parse an ActivityPub note received from another instance into a Lemmy comment /// Parse an ActivityPub note received from another instance into a Lemmy comment
fn from_apub(note: &Note, conn: &PgConnection) -> Result<CommentForm, Error> { async fn from_apub(
note: &Note,
client: &Client,
pool: &DbPool,
) -> Result<CommentForm, LemmyError> {
let oprops = &note.object_props; let oprops = &note.object_props;
let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string(); let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?;
let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, client, pool).await?;
let mut in_reply_tos = oprops.get_many_in_reply_to_xsd_any_uris().unwrap(); let mut in_reply_tos = oprops.get_many_in_reply_to_xsd_any_uris().unwrap();
let post_ap_id = in_reply_tos.next().unwrap().to_string(); let post_ap_id = in_reply_tos.next().unwrap().to_string();
// This post, or the parent comment might not yet exist on this server yet, fetch them. // This post, or the parent comment might not yet exist on this server yet, fetch them.
let post = get_or_fetch_and_insert_remote_post(&post_ap_id, &conn)?; let post = get_or_fetch_and_insert_remote_post(&post_ap_id, client, pool).await?;
// The 2nd item, if it exists, is the parent comment apub_id // 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<i32> = 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.to_string(); let parent_comment_ap_id = &parent_comment_uri.to_string();
let parent_comment = get_or_fetch_and_insert_remote_comment(&parent_comment_ap_id, &conn)?; let parent_comment =
get_or_fetch_and_insert_remote_comment(&parent_comment_ap_id, client, pool).await?;
Some(parent_comment.id) Some(parent_comment.id)
} }
@ -157,17 +174,27 @@ impl FromApub for CommentForm {
} }
} }
#[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. /// Send out information about a newly created comment, to the followers of the community.
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_create(
let note = self.to_apub(conn)?; &self,
let post = Post::read(&conn, self.post_id)?; creator: &User_,
let community = Community::read(conn, post.community_id)?; client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let post_id = self.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let maa =
collect_non_local_mentions_and_addresses(&self.content, &community, client, pool).await?;
let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
let maa: MentionsAndAddresses =
collect_non_local_mentions_and_addresses(&conn, &self.content, &community)?;
let mut create = Create::new(); let mut create = Create::new();
populate_object_props(&mut create.object_props, maa.addressed_ccs, &id)?; populate_object_props(&mut create.object_props, maa.addressed_ccs, &id)?;
@ -179,20 +206,29 @@ impl ApubObjectType for Comment {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(note)?; .set_object_base_box(note)?;
send_activity_to_community(&creator, &conn, &community, maa.inboxes, create)?; send_activity_to_community(&creator, &community, maa.inboxes, create, client, pool).await?;
Ok(()) Ok(())
} }
/// Send out information about an edited post, to the followers of the community. /// Send out information about an edited post, to the followers of the community.
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_update(
let note = self.to_apub(&conn)?; &self,
let post = Post::read(&conn, self.post_id)?; creator: &User_,
let community = Community::read(&conn, post.community_id)?; client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let post_id = self.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let maa =
collect_non_local_mentions_and_addresses(&self.content, &community, client, pool).await?;
let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
let maa: MentionsAndAddresses =
collect_non_local_mentions_and_addresses(&conn, &self.content, &community)?;
let mut update = Update::new(); let mut update = Update::new();
populate_object_props(&mut update.object_props, maa.addressed_ccs, &id)?; populate_object_props(&mut update.object_props, maa.addressed_ccs, &id)?;
@ -204,14 +240,24 @@ impl ApubObjectType for Comment {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(note)?; .set_object_base_box(note)?;
send_activity_to_community(&creator, &conn, &community, maa.inboxes, update)?; send_activity_to_community(&creator, &community, maa.inboxes, update, client, pool).await?;
Ok(()) Ok(())
} }
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_delete(
let note = self.to_apub(&conn)?; &self,
let post = Post::read(&conn, self.post_id)?; creator: &User_,
let community = Community::read(&conn, post.community_id)?; client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let post_id = self.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
let mut delete = Delete::default(); let mut delete = Delete::default();
@ -228,18 +274,29 @@ impl ApubObjectType for Comment {
send_activity_to_community( send_activity_to_community(
&creator, &creator,
&conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
delete, delete,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_undo_delete(
let note = self.to_apub(&conn)?; &self,
let post = Post::read(&conn, self.post_id)?; creator: &User_,
let community = Community::read(&conn, post.community_id)?; client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let post_id = self.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
// Generate a fake delete activity, with the correct object // Generate a fake delete activity, with the correct object
let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
@ -274,18 +331,30 @@ impl ApubObjectType for Comment {
send_activity_to_community( send_activity_to_community(
&creator, &creator,
&conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
undo, undo,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_remove(
let note = self.to_apub(&conn)?; &self,
let post = Post::read(&conn, self.post_id)?; mod_: &User_,
let community = Community::read(&conn, post.community_id)?; client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let post_id = self.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
let mut remove = Remove::default(); let mut remove = Remove::default();
@ -302,18 +371,29 @@ impl ApubObjectType for Comment {
send_activity_to_community( send_activity_to_community(
&mod_, &mod_,
&conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
remove, remove,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_undo_remove(
let note = self.to_apub(&conn)?; &self,
let post = Post::read(&conn, self.post_id)?; mod_: &User_,
let community = Community::read(&conn, post.community_id)?; client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let post_id = self.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
// Generate a fake delete activity, with the correct object // Generate a fake delete activity, with the correct object
let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
@ -347,20 +427,33 @@ impl ApubObjectType for Comment {
send_activity_to_community( send_activity_to_community(
&mod_, &mod_,
&conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
undo, undo,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
} }
#[async_trait::async_trait(?Send)]
impl ApubLikeableType for Comment { impl ApubLikeableType for Comment {
fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_like(
let note = self.to_apub(&conn)?; &self,
let post = Post::read(&conn, self.post_id)?; creator: &User_,
let community = Community::read(&conn, post.community_id)?; client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let post_id = self.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
let mut like = Like::new(); let mut like = Like::new();
@ -376,18 +469,30 @@ impl ApubLikeableType for Comment {
send_activity_to_community( send_activity_to_community(
&creator, &creator,
&conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
like, like,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_dislike(
let note = self.to_apub(&conn)?; &self,
let post = Post::read(&conn, self.post_id)?; creator: &User_,
let community = Community::read(&conn, post.community_id)?; client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let post_id = self.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
let mut dislike = Dislike::new(); let mut dislike = Dislike::new();
@ -403,18 +508,30 @@ impl ApubLikeableType for Comment {
send_activity_to_community( send_activity_to_community(
&creator, &creator,
&conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
dislike, dislike,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_undo_like(
let note = self.to_apub(&conn)?; &self,
let post = Post::read(&conn, self.post_id)?; creator: &User_,
let community = Community::read(&conn, post.community_id)?; client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let post_id = self.post_id;
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
let community_id = post.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
let mut like = Like::new(); let mut like = Like::new();
@ -446,11 +563,13 @@ impl ApubLikeableType for Comment {
send_activity_to_community( send_activity_to_community(
&creator, &creator,
&conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
undo, undo,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
} }
@ -464,11 +583,12 @@ struct 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 users / addresses that go in the cc field. /// Addresses are the users / addresses that go in the cc field.
fn collect_non_local_mentions_and_addresses( async fn collect_non_local_mentions_and_addresses(
conn: &PgConnection,
content: &str, content: &str,
community: &Community, community: &Community,
) -> Result<MentionsAndAddresses, Error> { client: &Client,
pool: &DbPool,
) -> Result<MentionsAndAddresses, LemmyError> {
let mut addressed_ccs = vec![community.get_followers_url()]; let mut addressed_ccs = vec![community.get_followers_url()];
// Add the mention tag // Add the mention tag
@ -480,14 +600,17 @@ fn collect_non_local_mentions_and_addresses(
// Filter only the non-local ones // Filter only the non-local ones
.filter(|m| !m.is_local()) .filter(|m| !m.is_local())
.collect::<Vec<MentionData>>(); .collect::<Vec<MentionData>>();
let mut mention_inboxes = Vec::new(); let mut mention_inboxes = Vec::new();
for mention in &mentions { for mention in &mentions {
// TODO should it be fetching it every time? // TODO should it be fetching it every time?
if let Ok(actor_id) = fetch_webfinger_url(mention) { if let Ok(actor_id) = fetch_webfinger_url(mention, client).await {
debug!("mention actor_id: {}", actor_id); debug!("mention actor_id: {}", actor_id);
addressed_ccs.push(actor_id.to_owned()); addressed_ccs.push(actor_id.to_owned());
let mention_user = get_or_fetch_and_upsert_remote_user(&actor_id, &conn)?;
let mention_user = get_or_fetch_and_upsert_remote_user(&actor_id, client, pool).await?;
let shared_inbox = mention_user.get_shared_inbox_url(); let shared_inbox = mention_user.get_shared_inbox_url();
mention_inboxes.push(shared_inbox); mention_inboxes.push(shared_inbox);
let mut mention_tag = Mention::new(); let mut mention_tag = Mention::new();
mention_tag mention_tag

View file

@ -12,6 +12,7 @@ use crate::{
GroupExt, GroupExt,
ToApub, ToApub,
}, },
blocking,
convert_datetime, convert_datetime,
db::{ db::{
activity::insert_activity, activity::insert_activity,
@ -21,6 +22,8 @@ use crate::{
}, },
naive_now, naive_now,
routes::DbPoolParam, routes::DbPoolParam,
DbPool,
LemmyError,
}; };
use activitystreams::{ use activitystreams::{
activity::{Accept, Announce, Delete, Remove, Undo}, activity::{Accept, Announce, Delete, Remove, Undo},
@ -35,22 +38,22 @@ use activitystreams::{
}; };
use activitystreams_ext::Ext3; use activitystreams_ext::Ext3;
use activitystreams_new::{activity::Follow, object::Tombstone}; use activitystreams_new::{activity::Follow, object::Tombstone};
use actix_web::{body::Body, web::Path, HttpResponse, Result}; use actix_web::{body::Body, client::Client, web, HttpResponse};
use diesel::PgConnection;
use failure::{Error, _core::fmt::Debug};
use itertools::Itertools; use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CommunityQuery { pub struct CommunityQuery {
community_name: String, community_name: String,
} }
#[async_trait::async_trait(?Send)]
impl ToApub for Community { impl ToApub for Community {
type Response = GroupExt; type Response = GroupExt;
// Turn a Lemmy Community into an ActivityPub group that can be sent out over the network. // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
fn to_apub(&self, conn: &PgConnection) -> Result<GroupExt, Error> { async fn to_apub(&self, pool: &DbPool) -> Result<GroupExt, LemmyError> {
let mut group = Group::default(); let mut group = Group::default();
let oprops: &mut ObjectProperties = group.as_mut(); let oprops: &mut ObjectProperties = group.as_mut();
@ -58,10 +61,12 @@ impl ToApub for Community {
// then the rest of the moderators // then the rest of the moderators
// TODO Technically the instance admins can mod the community, but lets // TODO Technically the instance admins can mod the community, but lets
// ignore that for now // ignore that for now
let moderators = CommunityModeratorView::for_community(&conn, self.id)? let id = self.id;
.into_iter() let moderators = blocking(pool, move |conn| {
.map(|m| m.user_actor_id) CommunityModeratorView::for_community(&conn, id)
.collect(); })
.await??;
let moderators = moderators.into_iter().map(|m| m.user_actor_id).collect();
oprops oprops
.set_context_xsd_any_uri(context())? .set_context_xsd_any_uri(context())?
@ -92,7 +97,12 @@ impl ToApub for Community {
.set_endpoints(endpoint_props)? .set_endpoints(endpoint_props)?
.set_followers(self.get_followers_url())?; .set_followers(self.get_followers_url())?;
let group_extension = GroupExtension::new(conn, self.category_id, self.nsfw)?; let nsfw = self.nsfw;
let category_id = self.category_id;
let group_extension = blocking(pool, move |conn| {
GroupExtension::new(conn, category_id, nsfw)
})
.await??;
Ok(Ext3::new( Ok(Ext3::new(
group, group,
@ -102,7 +112,7 @@ impl ToApub for Community {
)) ))
} }
fn to_tombstone(&self) -> Result<Tombstone, Error> { fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
create_tombstone( create_tombstone(
self.deleted, self.deleted,
&self.actor_id, &self.actor_id,
@ -112,6 +122,7 @@ impl ToApub for Community {
} }
} }
#[async_trait::async_trait(?Send)]
impl ActorType for Community { impl ActorType for Community {
fn actor_id(&self) -> String { fn actor_id(&self) -> String {
self.actor_id.to_owned() self.actor_id.to_owned()
@ -125,7 +136,12 @@ impl ActorType for Community {
} }
/// As a local community, accept the follow request from a remote user. /// As a local community, accept the follow request from a remote user.
fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error> { async fn send_accept_follow(
&self,
follow: &Follow,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let actor_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string(); let actor_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string();
let id = format!("{}/accept/{}", self.actor_id, uuid::Uuid::new_v4()); let id = format!("{}/accept/{}", self.actor_id, uuid::Uuid::new_v4());
@ -140,14 +156,20 @@ impl ActorType for Community {
.set_object_base_box(BaseBox::from_concrete(follow.clone())?)?; .set_object_base_box(BaseBox::from_concrete(follow.clone())?)?;
let to = format!("{}/inbox", actor_uri); let to = format!("{}/inbox", actor_uri);
insert_activity(&conn, self.creator_id, &accept, true)?; insert_activity(self.creator_id, accept.clone(), true, pool).await?;
send_activity(&accept, self, vec![to])?; send_activity(client, &accept, self, vec![to]).await?;
Ok(()) Ok(())
} }
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_delete(
let group = self.to_apub(conn)?; &self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let group = self.to_apub(pool).await?;
let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4()); let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4());
let mut delete = Delete::default(); let mut delete = Delete::default();
@ -162,17 +184,25 @@ impl ActorType for Community {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(BaseBox::from_concrete(group)?)?; .set_object_base_box(BaseBox::from_concrete(group)?)?;
insert_activity(&conn, self.creator_id, &delete, true)?; insert_activity(self.creator_id, delete.clone(), true, pool).await?;
let inboxes = self.get_follower_inboxes(pool).await?;
// Note: For an accept, since it was automatic, no one pushed a button, // Note: For an accept, since it was automatic, no one pushed a button,
// the community was the actor. // the community was the actor.
// But for delete, the creator is the actor, and does the signing // But for delete, the creator is the actor, and does the signing
send_activity(&delete, creator, self.get_follower_inboxes(&conn)?)?; send_activity(client, &delete, creator, inboxes).await?;
Ok(()) Ok(())
} }
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_undo_delete(
let group = self.to_apub(conn)?; &self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let group = self.to_apub(pool).await?;
let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4()); let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4());
let mut delete = Delete::default(); let mut delete = Delete::default();
@ -203,17 +233,25 @@ impl ActorType for Community {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(delete)?; .set_object_base_box(delete)?;
insert_activity(&conn, self.creator_id, &undo, true)?; insert_activity(self.creator_id, undo.clone(), true, pool).await?;
let inboxes = self.get_follower_inboxes(pool).await?;
// Note: For an accept, since it was automatic, no one pushed a button, // Note: For an accept, since it was automatic, no one pushed a button,
// the community was the actor. // the community was the actor.
// But for delete, the creator is the actor, and does the signing // But for delete, the creator is the actor, and does the signing
send_activity(&undo, creator, self.get_follower_inboxes(&conn)?)?; send_activity(client, &undo, creator, inboxes).await?;
Ok(()) Ok(())
} }
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_remove(
let group = self.to_apub(conn)?; &self,
mod_: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let group = self.to_apub(pool).await?;
let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4()); let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4());
let mut remove = Remove::default(); let mut remove = Remove::default();
@ -228,17 +266,25 @@ impl ActorType for Community {
.set_actor_xsd_any_uri(mod_.actor_id.to_owned())? .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
.set_object_base_box(BaseBox::from_concrete(group)?)?; .set_object_base_box(BaseBox::from_concrete(group)?)?;
insert_activity(&conn, mod_.id, &remove, true)?; insert_activity(mod_.id, remove.clone(), true, pool).await?;
let inboxes = self.get_follower_inboxes(pool).await?;
// Note: For an accept, since it was automatic, no one pushed a button, // Note: For an accept, since it was automatic, no one pushed a button,
// the community was the actor. // the community was the actor.
// But for delete, the creator is the actor, and does the signing // But for delete, the creator is the actor, and does the signing
send_activity(&remove, mod_, self.get_follower_inboxes(&conn)?)?; send_activity(client, &remove, mod_, inboxes).await?;
Ok(()) Ok(())
} }
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_undo_remove(
let group = self.to_apub(conn)?; &self,
mod_: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let group = self.to_apub(pool).await?;
let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4()); let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4());
let mut remove = Remove::default(); let mut remove = Remove::default();
@ -268,51 +314,69 @@ impl ActorType for Community {
.set_actor_xsd_any_uri(mod_.actor_id.to_owned())? .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
.set_object_base_box(remove)?; .set_object_base_box(remove)?;
insert_activity(&conn, mod_.id, &undo, true)?; insert_activity(mod_.id, undo.clone(), true, pool).await?;
let inboxes = self.get_follower_inboxes(pool).await?;
// Note: For an accept, since it was automatic, no one pushed a button, // Note: For an accept, since it was automatic, no one pushed a button,
// the community was the actor. // the community was the actor.
// But for remove , the creator is the actor, and does the signing // But for remove , the creator is the actor, and does the signing
send_activity(&undo, mod_, self.get_follower_inboxes(&conn)?)?; send_activity(client, &undo, mod_, inboxes).await?;
Ok(()) Ok(())
} }
/// For a given community, returns the inboxes of all followers. /// For a given community, returns the inboxes of all followers.
fn get_follower_inboxes(&self, conn: &PgConnection) -> Result<Vec<String>, Error> { async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<String>, LemmyError> {
Ok( let id = self.id;
CommunityFollowerView::for_community(conn, self.id)?
.into_iter() let inboxes = blocking(pool, move |conn| {
.map(|c| get_shared_inbox(&c.user_actor_id)) CommunityFollowerView::for_community(conn, id)
.filter(|s| !s.is_empty()) })
.unique() .await??;
.collect(), let inboxes = inboxes
) .into_iter()
.map(|c| get_shared_inbox(&c.user_actor_id))
.filter(|s| !s.is_empty())
.unique()
.collect();
Ok(inboxes)
} }
fn send_follow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> { async fn send_follow(
&self,
_follow_actor_id: &str,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
unimplemented!() unimplemented!()
} }
fn send_unfollow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> { async fn send_unfollow(
&self,
_follow_actor_id: &str,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
unimplemented!() unimplemented!()
} }
} }
#[async_trait::async_trait(?Send)]
impl FromApub for CommunityForm { impl FromApub for CommunityForm {
type ApubType = GroupExt; type ApubType = GroupExt;
/// Parse an ActivityPub group received from another instance into a Lemmy community. /// Parse an ActivityPub group received from another instance into a Lemmy community.
fn from_apub(group: &GroupExt, conn: &PgConnection) -> Result<Self, Error> { async fn from_apub(group: &GroupExt, client: &Client, pool: &DbPool) -> Result<Self, LemmyError> {
let group_extensions: &GroupExtension = &group.ext_one; let group_extensions: &GroupExtension = &group.ext_one;
let oprops = &group.inner.object_props; let oprops = &group.inner.object_props;
let aprops = &group.ext_two; let aprops = &group.ext_two;
let public_key: &PublicKey = &group.ext_three.public_key; let public_key: &PublicKey = &group.ext_three.public_key;
let mut creator_and_moderator_uris = oprops.get_many_attributed_to_xsd_any_uris().unwrap(); let mut creator_and_moderator_uris = oprops.get_many_attributed_to_xsd_any_uris().unwrap();
let creator = creator_and_moderator_uris let creator_uri = creator_and_moderator_uris.next().unwrap();
.next()
.map(|c| get_or_fetch_and_upsert_remote_user(&c.to_string(), &conn).unwrap()) let creator = get_or_fetch_and_upsert_remote_user(creator_uri.as_str(), client, pool).await?;
.unwrap();
Ok(CommunityForm { Ok(CommunityForm {
name: oprops.get_name_xsd_string().unwrap().to_string(), name: oprops.get_name_xsd_string().unwrap().to_string(),
@ -342,14 +406,18 @@ impl FromApub for CommunityForm {
/// Return the community json over HTTP. /// Return the community json over HTTP.
pub async fn get_apub_community_http( pub async fn get_apub_community_http(
info: Path<CommunityQuery>, info: web::Path<CommunityQuery>,
db: DbPoolParam, db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> { ) -> Result<HttpResponse<Body>, LemmyError> {
let community = Community::read_from_name(&&db.get()?, &info.community_name)?; let community = blocking(&db, move |conn| {
Community::read_from_name(conn, &info.community_name)
})
.await??;
if !community.deleted { if !community.deleted {
Ok(create_apub_response( let apub = community.to_apub(&db).await?;
&community.to_apub(&db.get().unwrap())?,
)) Ok(create_apub_response(&apub))
} else { } else {
Ok(create_apub_tombstone_response(&community.to_tombstone()?)) Ok(create_apub_tombstone_response(&community.to_tombstone()?))
} }
@ -357,15 +425,19 @@ pub async fn get_apub_community_http(
/// Returns an empty followers collection, only populating the size (for privacy). /// Returns an empty followers collection, only populating the size (for privacy).
pub async fn get_apub_community_followers( pub async fn get_apub_community_followers(
info: Path<CommunityQuery>, info: web::Path<CommunityQuery>,
db: DbPoolParam, db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> { ) -> Result<HttpResponse<Body>, LemmyError> {
let community = Community::read_from_name(&&db.get()?, &info.community_name)?; let community = blocking(&db, move |conn| {
Community::read_from_name(&conn, &info.community_name)
})
.await??;
let conn = db.get()?; let community_id = community.id;
let community_followers = blocking(&db, move |conn| {
//As we are an object, we validated that the community id was valid CommunityFollowerView::for_community(&conn, community_id)
let community_followers = CommunityFollowerView::for_community(&conn, community.id).unwrap(); })
.await??;
let mut collection = UnorderedCollection::default(); let mut collection = UnorderedCollection::default();
let oprops: &mut ObjectProperties = collection.as_mut(); let oprops: &mut ObjectProperties = collection.as_mut();
@ -379,12 +451,13 @@ pub async fn get_apub_community_followers(
} }
impl Community { impl Community {
pub fn do_announce<A>( pub async fn do_announce<A>(
activity: A, activity: A,
community: &Community, community: &Community,
sender: &dyn ActorType, sender: &dyn ActorType,
conn: &PgConnection, client: &Client,
) -> Result<HttpResponse, Error> pool: &DbPool,
) -> Result<HttpResponse, LemmyError>
where where
A: Activity + Base + Serialize + Debug, A: Activity + Base + Serialize + Debug,
{ {
@ -399,15 +472,16 @@ impl Community {
.set_actor_xsd_any_uri(community.actor_id.to_owned())? .set_actor_xsd_any_uri(community.actor_id.to_owned())?
.set_object_base_box(BaseBox::from_concrete(activity)?)?; .set_object_base_box(BaseBox::from_concrete(activity)?)?;
insert_activity(&conn, community.creator_id, &announce, true)?; insert_activity(community.creator_id, announce.clone(), true, pool).await?;
// dont send to the instance where the activity originally came from, because that would result // dont send to the instance where the activity originally came from, because that would result
// in a database error (same data inserted twice) // in a database error (same data inserted twice)
let mut to = community.get_follower_inboxes(&conn)?; let mut to = community.get_follower_inboxes(pool).await?;
// this seems to be the "easiest" stable alternative for remove_item() // this seems to be the "easiest" stable alternative for remove_item()
to.retain(|x| *x != sender.get_shared_inbox_url()); to.retain(|x| *x != sender.get_shared_inbox_url());
send_activity(&announce, community, to)?; send_activity(client, &announce, community, to).await?;
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }

View file

@ -4,6 +4,7 @@ use crate::{
fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user}, fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
ActorType, ActorType,
}, },
blocking,
db::{ db::{
activity::insert_activity, activity::insert_activity,
community::{Community, CommunityFollower, CommunityFollowerForm}, community::{Community, CommunityFollower, CommunityFollowerForm},
@ -11,14 +12,14 @@ use crate::{
Followable, Followable,
}, },
routes::{ChatServerParam, DbPoolParam}, routes::{ChatServerParam, DbPoolParam},
LemmyError,
}; };
use activitystreams::activity::Undo; use activitystreams::activity::Undo;
use activitystreams_new::activity::Follow; use activitystreams_new::activity::Follow;
use actix_web::{web, HttpRequest, HttpResponse, Result}; use actix_web::{client::Client, web, HttpRequest, HttpResponse};
use diesel::PgConnection;
use failure::{Error, _core::fmt::Debug};
use log::debug; use log::debug;
use serde::Deserialize; use serde::Deserialize;
use std::fmt::Debug;
#[serde(untagged)] #[serde(untagged)]
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@ -28,7 +29,7 @@ pub enum CommunityAcceptedObjects {
} }
impl CommunityAcceptedObjects { impl CommunityAcceptedObjects {
fn follow(&self) -> Result<Follow, Error> { fn follow(&self) -> Result<Follow, LemmyError> {
match self { match self {
CommunityAcceptedObjects::Follow(f) => Ok(f.to_owned()), CommunityAcceptedObjects::Follow(f) => Ok(f.to_owned()),
CommunityAcceptedObjects::Undo(u) => Ok( CommunityAcceptedObjects::Undo(u) => Ok(
@ -49,16 +50,22 @@ pub async fn community_inbox(
input: web::Json<CommunityAcceptedObjects>, input: web::Json<CommunityAcceptedObjects>,
path: web::Path<String>, path: web::Path<String>,
db: DbPoolParam, db: DbPoolParam,
client: web::Data<Client>,
_chat_server: ChatServerParam, _chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, LemmyError> {
let input = input.into_inner(); let input = input.into_inner();
let conn = db.get()?;
let community = Community::read_from_name(&conn, &path.into_inner())?; let path = path.into_inner();
let community = blocking(&db, move |conn| Community::read_from_name(&conn, &path)).await??;
if !community.local { if !community.local {
return Err(format_err!( return Err(
"Received activity is addressed to remote community {}", format_err!(
&community.actor_id "Received activity is addressed to remote community {}",
)); &community.actor_id
)
.into(),
);
} }
debug!( debug!(
"Community {} received activity {:?}", "Community {} received activity {:?}",
@ -68,28 +75,27 @@ pub async fn community_inbox(
let user_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string(); let user_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string();
let community_uri = follow.object.as_single_xsd_any_uri().unwrap().to_string(); let community_uri = follow.object.as_single_xsd_any_uri().unwrap().to_string();
let conn = db.get()?; let user = get_or_fetch_and_upsert_remote_user(&user_uri, &client, &db).await?;
let community = get_or_fetch_and_upsert_remote_community(&community_uri, &client, &db).await?;
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
let community = get_or_fetch_and_upsert_remote_community(&community_uri, &conn)?;
verify(&request, &user)?; verify(&request, &user)?;
match input { match input {
CommunityAcceptedObjects::Follow(f) => handle_follow(&f, &user, &community, &conn), CommunityAcceptedObjects::Follow(f) => handle_follow(f, user, community, &client, db).await,
CommunityAcceptedObjects::Undo(u) => handle_undo_follow(&u, &user, &community, &conn), CommunityAcceptedObjects::Undo(u) => handle_undo_follow(u, user, community, db).await,
} }
} }
/// Handle a follow request from a remote user, adding it to the local database and returning an /// Handle a follow request from a remote user, adding it to the local database and returning an
/// Accept activity. /// Accept activity.
fn handle_follow( async fn handle_follow(
follow: &Follow, follow: Follow,
user: &User_, user: User_,
community: &Community, community: Community,
conn: &PgConnection, client: &Client,
) -> Result<HttpResponse, Error> { db: DbPoolParam,
insert_activity(&conn, user.id, &follow, false)?; ) -> Result<HttpResponse, LemmyError> {
insert_activity(user.id, follow.clone(), false, &db).await?;
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: community.id, community_id: community.id,
@ -97,27 +103,34 @@ fn handle_follow(
}; };
// This will fail if they're already a follower, but ignore the error. // This will fail if they're already a follower, but ignore the error.
CommunityFollower::follow(&conn, &community_follower_form).ok(); blocking(&db, move |conn| {
CommunityFollower::follow(&conn, &community_follower_form).ok()
})
.await?;
community.send_accept_follow(&follow, &conn)?; community.send_accept_follow(&follow, &client, &db).await?;
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
fn handle_undo_follow( async fn handle_undo_follow(
undo: &Undo, undo: Undo,
user: &User_, user: User_,
community: &Community, community: Community,
conn: &PgConnection, db: DbPoolParam,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, LemmyError> {
insert_activity(&conn, user.id, &undo, false)?; insert_activity(user.id, undo, false, &db).await?;
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: community.id, community_id: community.id,
user_id: user.id, user_id: user.id,
}; };
CommunityFollower::unfollow(&conn, &community_follower_form).ok(); // This will fail if they aren't a follower, but ignore the error.
blocking(&db, move |conn| {
CommunityFollower::unfollow(&conn, &community_follower_form).ok()
})
.await?;
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }

View file

@ -1,7 +1,9 @@
use crate::db::{category::Category, Crud}; use crate::{
db::{category::Category, Crud},
LemmyError,
};
use activitystreams::{ext::Extension, Actor}; use activitystreams::{ext::Extension, Actor};
use diesel::PgConnection; use diesel::PgConnection;
use failure::Error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
@ -24,7 +26,7 @@ impl GroupExtension {
conn: &PgConnection, conn: &PgConnection,
category_id: i32, category_id: i32,
sensitive: bool, sensitive: bool,
) -> Result<GroupExtension, Error> { ) -> Result<GroupExtension, LemmyError> {
let category = Category::read(conn, category_id)?; let category = Category::read(conn, category_id)?;
let group_category = GroupCategory { let group_category = GroupCategory {
identifier: category_id.to_string(), identifier: category_id.to_string(),

View file

@ -1,9 +1,10 @@
use crate::apub::ActorType; use crate::{apub::ActorType, LemmyError};
use activitystreams::ext::Extension; use activitystreams::ext::Extension;
use actix_web::HttpRequest; use actix_web::{client::ClientRequest, HttpRequest};
use failure::Error; use http_signature_normalization_actix::{
use http::request::Builder; digest::{DigestClient, SignExt},
use http_signature_normalization::Config; Config,
};
use log::debug; use log::debug;
use openssl::{ use openssl::{
hash::MessageDigest, hash::MessageDigest,
@ -12,7 +13,7 @@ use openssl::{
sign::{Signer, Verifier}, sign::{Signer, Verifier},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use sha2::{Digest, Sha256};
lazy_static! { lazy_static! {
static ref HTTP_SIG_CONFIG: Config = Config::new(); static ref HTTP_SIG_CONFIG: Config = Config::new();
@ -24,7 +25,7 @@ pub struct Keypair {
} }
/// Generate the asymmetric keypair for ActivityPub HTTP signatures. /// Generate the asymmetric keypair for ActivityPub HTTP signatures.
pub fn generate_actor_keypair() -> Result<Keypair, Error> { pub fn generate_actor_keypair() -> Result<Keypair, LemmyError> {
let rsa = Rsa::generate(2048)?; let rsa = Rsa::generate(2048)?;
let pkey = PKey::from_rsa(rsa)?; let pkey = PKey::from_rsa(rsa)?;
let public_key = pkey.public_key_to_pem()?; let public_key = pkey.public_key_to_pem()?;
@ -36,56 +37,41 @@ pub fn generate_actor_keypair() -> Result<Keypair, Error> {
} }
/// Signs request headers with the given keypair. /// Signs request headers with the given keypair.
pub fn sign(request: &Builder, actor: &dyn ActorType) -> Result<String, Error> { pub async fn sign(
request: ClientRequest,
actor: &dyn ActorType,
activity: String,
) -> Result<DigestClient<String>, LemmyError> {
let signing_key_id = format!("{}#main-key", actor.actor_id()); let signing_key_id = format!("{}#main-key", actor.actor_id());
let private_key = actor.private_key();
let headers = request let digest_client = request
.headers_ref() .signature_with_digest(
.unwrap() HTTP_SIG_CONFIG.clone(),
.iter() signing_key_id,
.map(|h| -> Result<(String, String), Error> { Sha256::new(),
Ok((h.0.as_str().to_owned(), h.1.to_str()?.to_owned())) activity,
}) move |signing_string| {
.collect::<Result<BTreeMap<String, String>, Error>>()?; let private_key = PKey::private_key_from_pem(private_key.as_bytes())?;
let mut signer = Signer::new(MessageDigest::sha256(), &private_key).unwrap();
signer.update(signing_string.as_bytes()).unwrap();
let signature_header_value = HTTP_SIG_CONFIG Ok(base64::encode(signer.sign_to_vec()?)) as Result<_, LemmyError>
.begin_sign( },
request.method_ref().unwrap().as_str(), )
request .await?;
.uri_ref()
.unwrap()
.path_and_query()
.unwrap()
.as_str(),
headers,
)?
.sign(signing_key_id, |signing_string| {
let private_key = PKey::private_key_from_pem(actor.private_key().as_bytes())?;
let mut signer = Signer::new(MessageDigest::sha256(), &private_key).unwrap();
signer.update(signing_string.as_bytes()).unwrap();
Ok(base64::encode(signer.sign_to_vec()?)) as Result<_, Error>
})?
.signature_header();
Ok(signature_header_value) Ok(digest_client)
} }
pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), Error> { pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> {
let headers = request
.headers()
.iter()
.map(|h| -> Result<(String, String), Error> {
Ok((h.0.as_str().to_owned(), h.1.to_str()?.to_owned()))
})
.collect::<Result<BTreeMap<String, String>, Error>>()?;
let verified = HTTP_SIG_CONFIG let verified = HTTP_SIG_CONFIG
.begin_verify( .begin_verify(
request.method().as_str(), request.method(),
request.uri().path_and_query().unwrap().as_str(), request.uri().path_and_query(),
headers, request.headers().clone(),
)? )?
.verify(|signature, signing_string| -> Result<bool, Error> { .verify(|signature, signing_string| -> Result<bool, LemmyError> {
debug!( debug!(
"Verifying with key {}, message {}", "Verifying with key {}, message {}",
&actor.public_key(), &actor.public_key(),
@ -101,10 +87,7 @@ pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), Error>
debug!("verified signature for {}", &request.uri()); debug!("verified signature for {}", &request.uri());
Ok(()) Ok(())
} else { } else {
Err(format_err!( Err(format_err!("Invalid signature on request: {}", &request.uri()).into())
"Invalid signature on request: {}",
&request.uri()
))
} }
} }

View file

@ -1,15 +1,14 @@
use activitystreams::object::Note; use activitystreams::object::Note;
use actix_web::Result; use actix_web::client::Client;
use diesel::{result::Error::NotFound, PgConnection}; use diesel::{result::Error::NotFound, PgConnection};
use failure::{Error, _core::fmt::Debug};
use isahc::prelude::*;
use log::debug; use log::debug;
use serde::Deserialize; use serde::Deserialize;
use std::time::Duration; use std::{fmt::Debug, time::Duration};
use url::Url; use url::Url;
use crate::{ use crate::{
api::site::SearchResponse, api::site::SearchResponse,
blocking,
db::{ db::{
comment::{Comment, CommentForm}, comment::{Comment, CommentForm},
comment_view::CommentView, comment_view::CommentView,
@ -23,7 +22,10 @@ use crate::{
SearchType, SearchType,
}, },
naive_now, naive_now,
request::{retry, RecvError},
routes::nodeinfo::{NodeInfo, NodeInfoWellKnown}, routes::nodeinfo::{NodeInfo, NodeInfoWellKnown},
DbPool,
LemmyError,
}; };
use crate::{ use crate::{
@ -43,36 +45,50 @@ use chrono::NaiveDateTime;
static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60; static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60;
// Fetch nodeinfo metadata from a remote instance. // Fetch nodeinfo metadata from a remote instance.
fn _fetch_node_info(domain: &str) -> Result<NodeInfo, Error> { async fn _fetch_node_info(client: &Client, domain: &str) -> Result<NodeInfo, LemmyError> {
let well_known_uri = Url::parse(&format!( let well_known_uri = Url::parse(&format!(
"{}://{}/.well-known/nodeinfo", "{}://{}/.well-known/nodeinfo",
get_apub_protocol_string(), get_apub_protocol_string(),
domain domain
))?; ))?;
let well_known = fetch_remote_object::<NodeInfoWellKnown>(&well_known_uri)?;
Ok(fetch_remote_object::<NodeInfo>(&well_known.links.href)?) let well_known = fetch_remote_object::<NodeInfoWellKnown>(client, &well_known_uri).await?;
let nodeinfo = fetch_remote_object::<NodeInfo>(client, &well_known.links.href).await?;
Ok(nodeinfo)
} }
/// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation, /// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
/// timeouts etc. /// timeouts etc.
pub fn fetch_remote_object<Response>(url: &Url) -> Result<Response, Error> pub async fn fetch_remote_object<Response>(
client: &Client,
url: &Url,
) -> Result<Response, LemmyError>
where where
Response: for<'de> Deserialize<'de>, Response: for<'de> Deserialize<'de>,
{ {
if !is_apub_id_valid(&url) { if !is_apub_id_valid(&url) {
return Err(format_err!("Activitypub uri invalid or blocked: {}", url)); return Err(format_err!("Activitypub uri invalid or blocked: {}", url).into());
} }
// TODO: this function should return a future
let timeout = Duration::from_secs(60); let timeout = Duration::from_secs(60);
let text = Request::get(url.as_str())
.header("Accept", APUB_JSON_CONTENT_TYPE) let json = retry(|| {
.connect_timeout(timeout) client
.timeout(timeout) .get(url.as_str())
.body(())? .header("Accept", APUB_JSON_CONTENT_TYPE)
.send()? .timeout(timeout)
.text()?; .send()
let res: Response = serde_json::from_str(&text)?; })
Ok(res) .await?
.json()
.await
.map_err(|e| {
debug!("Receive error, {}", e);
RecvError(e.to_string())
})?;
Ok(json)
} }
/// The types of ActivityPub objects that can be fetched directly by searching for their ID. /// The types of ActivityPub objects that can be fetched directly by searching for their ID.
@ -92,7 +108,11 @@ pub enum SearchAcceptedObjects {
/// http://lemmy_alpha:8540/u/lemmy_alpha, or @lemmy_alpha@lemmy_alpha:8540 /// http://lemmy_alpha:8540/u/lemmy_alpha, or @lemmy_alpha@lemmy_alpha:8540
/// http://lemmy_alpha:8540/post/3 /// http://lemmy_alpha:8540/post/3
/// http://lemmy_alpha:8540/comment/2 /// http://lemmy_alpha:8540/comment/2
pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchResponse, Error> { pub async fn search_by_apub_id(
query: &str,
client: &Client,
pool: &DbPool,
) -> Result<SearchResponse, LemmyError> {
// Parse the shorthand query url // Parse the shorthand query url
let query_url = if query.contains('@') { let query_url = if query.contains('@') {
debug!("{}", query); debug!("{}", query);
@ -107,10 +127,10 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchRespo
let split2 = split[0].split('!').collect::<Vec<&str>>(); let split2 = split[0].split('!').collect::<Vec<&str>>();
(format!("/c/{}", split2[1]), split[1]) (format!("/c/{}", split2[1]), split[1])
} else { } else {
return Err(format_err!("Invalid search query: {}", query)); return Err(format_err!("Invalid search query: {}", query).into());
} }
} else { } else {
return Err(format_err!("Invalid search query: {}", query)); return Err(format_err!("Invalid search query: {}", query).into());
}; };
let url = format!("{}://{}{}", get_apub_protocol_string(), instance, name); let url = format!("{}://{}{}", get_apub_protocol_string(), instance, name);
@ -126,22 +146,41 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchRespo
communities: vec![], communities: vec![],
users: vec![], users: vec![],
}; };
match fetch_remote_object::<SearchAcceptedObjects>(&query_url)? {
let response = match fetch_remote_object::<SearchAcceptedObjects>(client, &query_url).await? {
SearchAcceptedObjects::Person(p) => { SearchAcceptedObjects::Person(p) => {
let user_uri = p.inner.object_props.get_id().unwrap().to_string(); let user_uri = p.inner.object_props.get_id().unwrap().to_string();
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
response.users = vec![UserView::read(conn, user.id)?]; let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
response.users = vec![blocking(pool, move |conn| UserView::read(conn, user.id)).await??];
response
} }
SearchAcceptedObjects::Group(g) => { SearchAcceptedObjects::Group(g) => {
let community_uri = g.inner.object_props.get_id().unwrap().to_string(); let community_uri = g.inner.object_props.get_id().unwrap().to_string();
let community = get_or_fetch_and_upsert_remote_community(&community_uri, &conn)?;
let community =
get_or_fetch_and_upsert_remote_community(&community_uri, client, pool).await?;
// TODO Maybe at some point in the future, fetch all the history of a community // TODO Maybe at some point in the future, fetch all the history of a community
// fetch_community_outbox(&c, conn)?; // fetch_community_outbox(&c, conn)?;
response.communities = vec![CommunityView::read(conn, community.id, None)?]; response.communities = vec![
blocking(pool, move |conn| {
CommunityView::read(conn, community.id, None)
})
.await??,
];
response
} }
SearchAcceptedObjects::Page(p) => { SearchAcceptedObjects::Page(p) => {
let p = upsert_post(&PostForm::from_apub(&p, conn)?, conn)?; let post_form = PostForm::from_apub(&p, client, pool).await?;
response.posts = vec![PostView::read(conn, p.id, None)?];
let p = blocking(pool, move |conn| upsert_post(&post_form, conn)).await??;
response.posts = vec![blocking(pool, move |conn| PostView::read(conn, p.id, None)).await??];
response
} }
SearchAcceptedObjects::Comment(c) => { SearchAcceptedObjects::Comment(c) => {
let post_url = c let post_url = c
@ -151,41 +190,59 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchRespo
.next() .next()
.unwrap() .unwrap()
.to_string(); .to_string();
// TODO: also fetch parent comments if any // TODO: also fetch parent comments if any
let post = fetch_remote_object(&Url::parse(&post_url)?)?; let post = fetch_remote_object(client, &Url::parse(&post_url)?).await?;
upsert_post(&PostForm::from_apub(&post, conn)?, conn)?; let post_form = PostForm::from_apub(&post, client, pool).await?;
let c = upsert_comment(&CommentForm::from_apub(&c, conn)?, conn)?; let comment_form = CommentForm::from_apub(&c, client, pool).await?;
response.comments = vec![CommentView::read(conn, c.id, None)?];
blocking(pool, move |conn| upsert_post(&post_form, conn)).await??;
let c = blocking(pool, move |conn| upsert_comment(&comment_form, conn)).await??;
response.comments =
vec![blocking(pool, move |conn| CommentView::read(conn, c.id, None)).await??];
response
} }
} };
Ok(response) Ok(response)
} }
/// Check if a remote user exists, create if not found, if its too old update it.Fetch a user, insert/update it in the database and return the user. /// Check if a remote user exists, create if not found, if its too old update it.Fetch a user, insert/update it in the database and return the user.
pub fn get_or_fetch_and_upsert_remote_user( pub async fn get_or_fetch_and_upsert_remote_user(
apub_id: &str, apub_id: &str,
conn: &PgConnection, client: &Client,
) -> Result<User_, Error> { pool: &DbPool,
match User_::read_from_actor_id(&conn, &apub_id) { ) -> Result<User_, LemmyError> {
Ok(u) => { let apub_id_owned = apub_id.to_owned();
// If its older than a day, re-fetch it let user = blocking(pool, move |conn| {
if !u.local && should_refetch_actor(u.last_refreshed_at) { User_::read_from_actor_id(conn, &apub_id_owned)
debug!("Fetching and updating from remote user: {}", apub_id); })
let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?; .await?;
let mut uf = UserForm::from_apub(&person, &conn)?;
uf.last_refreshed_at = Some(naive_now()); match user {
Ok(User_::update(&conn, u.id, &uf)?) // If its older than a day, re-fetch it
} else { Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => {
Ok(u) debug!("Fetching and updating from remote user: {}", apub_id);
} let person = fetch_remote_object::<PersonExt>(client, &Url::parse(apub_id)?).await?;
let mut uf = UserForm::from_apub(&person, client, pool).await?;
uf.last_refreshed_at = Some(naive_now());
let user = blocking(pool, move |conn| User_::update(conn, u.id, &uf)).await??;
Ok(user)
} }
Ok(u) => Ok(u),
Err(NotFound {}) => { Err(NotFound {}) => {
debug!("Fetching and creating remote user: {}", apub_id); debug!("Fetching and creating remote user: {}", apub_id);
let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?; let person = fetch_remote_object::<PersonExt>(client, &Url::parse(apub_id)?).await?;
let uf = UserForm::from_apub(&person, &conn)?;
Ok(User_::create(conn, &uf)?) let uf = UserForm::from_apub(&person, client, pool).await?;
let user = blocking(pool, move |conn| User_::create(conn, &uf)).await??;
Ok(user)
} }
Err(e) => Err(Error::from(e)), Err(e) => Err(e.into()),
} }
} }
@ -204,27 +261,35 @@ fn should_refetch_actor(last_refreshed: NaiveDateTime) -> bool {
} }
/// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community. /// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community.
pub fn get_or_fetch_and_upsert_remote_community( pub async fn get_or_fetch_and_upsert_remote_community(
apub_id: &str, apub_id: &str,
conn: &PgConnection, client: &Client,
) -> Result<Community, Error> { pool: &DbPool,
match Community::read_from_actor_id(&conn, &apub_id) { ) -> Result<Community, LemmyError> {
Ok(c) => { let apub_id_owned = apub_id.to_owned();
if !c.local && should_refetch_actor(c.last_refreshed_at) { let community = blocking(pool, move |conn| {
debug!("Fetching and updating from remote community: {}", apub_id); Community::read_from_actor_id(conn, &apub_id_owned)
let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?; })
let mut cf = CommunityForm::from_apub(&group, conn)?; .await?;
cf.last_refreshed_at = Some(naive_now());
Ok(Community::update(&conn, c.id, &cf)?) match community {
} else { Ok(c) if !c.local && should_refetch_actor(c.last_refreshed_at) => {
Ok(c) debug!("Fetching and updating from remote community: {}", apub_id);
} let group = fetch_remote_object::<GroupExt>(client, &Url::parse(apub_id)?).await?;
let mut cf = CommunityForm::from_apub(&group, client, pool).await?;
cf.last_refreshed_at = Some(naive_now());
let community = blocking(pool, move |conn| Community::update(conn, c.id, &cf)).await??;
Ok(community)
} }
Ok(c) => Ok(c),
Err(NotFound {}) => { Err(NotFound {}) => {
debug!("Fetching and creating remote community: {}", apub_id); debug!("Fetching and creating remote community: {}", apub_id);
let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?; let group = fetch_remote_object::<GroupExt>(client, &Url::parse(apub_id)?).await?;
let cf = CommunityForm::from_apub(&group, conn)?;
let community = Community::create(conn, &cf)?; let cf = CommunityForm::from_apub(&group, client, pool).await?;
let community = blocking(pool, move |conn| Community::create(conn, &cf)).await??;
// Also add the community moderators too // Also add the community moderators too
let creator_and_moderator_uris = group let creator_and_moderator_uris = group
@ -232,74 +297,105 @@ pub fn get_or_fetch_and_upsert_remote_community(
.object_props .object_props
.get_many_attributed_to_xsd_any_uris() .get_many_attributed_to_xsd_any_uris()
.unwrap(); .unwrap();
let creator_and_moderators = creator_and_moderator_uris
.map(|c| get_or_fetch_and_upsert_remote_user(&c.to_string(), &conn).unwrap())
.collect::<Vec<User_>>();
for mod_ in creator_and_moderators { let mut creator_and_moderators = Vec::new();
let community_moderator_form = CommunityModeratorForm {
community_id: community.id, for uri in creator_and_moderator_uris {
user_id: mod_.id, let c_or_m = get_or_fetch_and_upsert_remote_user(uri.as_str(), client, pool).await?;
};
CommunityModerator::join(&conn, &community_moderator_form)?; creator_and_moderators.push(c_or_m);
} }
let community_id = community.id;
blocking(pool, move |conn| {
for mod_ in creator_and_moderators {
let community_moderator_form = CommunityModeratorForm {
community_id,
user_id: mod_.id,
};
CommunityModerator::join(conn, &community_moderator_form)?;
}
Ok(()) as Result<(), LemmyError>
})
.await??;
Ok(community) Ok(community)
} }
Err(e) => Err(Error::from(e)), Err(e) => Err(e.into()),
} }
} }
fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, Error> { fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, LemmyError> {
let existing = Post::read_from_apub_id(conn, &post_form.ap_id); let existing = Post::read_from_apub_id(conn, &post_form.ap_id);
match existing { match existing {
Err(NotFound {}) => Ok(Post::create(conn, &post_form)?), Err(NotFound {}) => Ok(Post::create(conn, &post_form)?),
Ok(p) => Ok(Post::update(conn, p.id, &post_form)?), Ok(p) => Ok(Post::update(conn, p.id, &post_form)?),
Err(e) => Err(Error::from(e)), Err(e) => Err(e.into()),
} }
} }
pub fn get_or_fetch_and_insert_remote_post( pub async fn get_or_fetch_and_insert_remote_post(
post_ap_id: &str, post_ap_id: &str,
conn: &PgConnection, client: &Client,
) -> Result<Post, Error> { pool: &DbPool,
match Post::read_from_apub_id(conn, post_ap_id) { ) -> Result<Post, LemmyError> {
let post_ap_id_owned = post_ap_id.to_owned();
let post = blocking(pool, move |conn| {
Post::read_from_apub_id(conn, &post_ap_id_owned)
})
.await?;
match post {
Ok(p) => Ok(p), Ok(p) => Ok(p),
Err(NotFound {}) => { Err(NotFound {}) => {
debug!("Fetching and creating remote post: {}", post_ap_id); debug!("Fetching and creating remote post: {}", post_ap_id);
let post = fetch_remote_object::<PageExt>(&Url::parse(post_ap_id)?)?; let post = fetch_remote_object::<PageExt>(client, &Url::parse(post_ap_id)?).await?;
let post_form = PostForm::from_apub(&post, conn)?; let post_form = PostForm::from_apub(&post, client, pool).await?;
Ok(Post::create(conn, &post_form)?)
let post = blocking(pool, move |conn| Post::create(conn, &post_form)).await??;
Ok(post)
} }
Err(e) => Err(Error::from(e)), Err(e) => Err(e.into()),
} }
} }
fn upsert_comment(comment_form: &CommentForm, conn: &PgConnection) -> Result<Comment, Error> { fn upsert_comment(comment_form: &CommentForm, conn: &PgConnection) -> Result<Comment, LemmyError> {
let existing = Comment::read_from_apub_id(conn, &comment_form.ap_id); let existing = Comment::read_from_apub_id(conn, &comment_form.ap_id);
match existing { match existing {
Err(NotFound {}) => Ok(Comment::create(conn, &comment_form)?), Err(NotFound {}) => Ok(Comment::create(conn, &comment_form)?),
Ok(p) => Ok(Comment::update(conn, p.id, &comment_form)?), Ok(p) => Ok(Comment::update(conn, p.id, &comment_form)?),
Err(e) => Err(Error::from(e)), Err(e) => Err(e.into()),
} }
} }
pub fn get_or_fetch_and_insert_remote_comment( pub async fn get_or_fetch_and_insert_remote_comment(
comment_ap_id: &str, comment_ap_id: &str,
conn: &PgConnection, client: &Client,
) -> Result<Comment, Error> { pool: &DbPool,
match Comment::read_from_apub_id(conn, comment_ap_id) { ) -> Result<Comment, LemmyError> {
let comment_ap_id_owned = comment_ap_id.to_owned();
let comment = blocking(pool, move |conn| {
Comment::read_from_apub_id(conn, &comment_ap_id_owned)
})
.await?;
match comment {
Ok(p) => Ok(p), Ok(p) => Ok(p),
Err(NotFound {}) => { Err(NotFound {}) => {
debug!( debug!(
"Fetching and creating remote comment and its parents: {}", "Fetching and creating remote comment and its parents: {}",
comment_ap_id comment_ap_id
); );
let comment = fetch_remote_object::<Note>(&Url::parse(comment_ap_id)?)?; let comment = fetch_remote_object::<Note>(client, &Url::parse(comment_ap_id)?).await?;
let comment_form = CommentForm::from_apub(&comment, conn)?; let comment_form = CommentForm::from_apub(&comment, client, pool).await?;
Ok(Comment::create(conn, &comment_form)?)
let comment = blocking(pool, move |conn| Comment::create(conn, &comment_form)).await??;
Ok(comment)
} }
Err(e) => Err(Error::from(e)), Err(e) => Err(e.into()),
} }
} }
@ -309,7 +405,7 @@ pub fn get_or_fetch_and_insert_remote_comment(
// maybe), is community and user actors // maybe), is community and user actors
// and user actors // and user actors
// Fetch all posts in the outbox of the given user, and insert them into the database. // Fetch all posts in the outbox of the given user, and insert them into the database.
// fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result<Vec<Post>, Error> { // fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result<Vec<Post>, LemmyError> {
// let outbox_url = Url::parse(&community.get_outbox_url())?; // let outbox_url = Url::parse(&community.get_outbox_url())?;
// let outbox = fetch_remote_object::<OrderedCollection>(&outbox_url)?; // let outbox = fetch_remote_object::<OrderedCollection>(&outbox_url)?;
// let items = outbox.collection_props.get_many_items_base_boxes(); // let items = outbox.collection_props.get_many_items_base_boxes();
@ -317,11 +413,11 @@ pub fn get_or_fetch_and_insert_remote_comment(
// Ok( // Ok(
// items // items
// .unwrap() // .unwrap()
// .map(|obox: &BaseBox| -> Result<PostForm, Error> { // .map(|obox: &BaseBox| -> Result<PostForm, LemmyError> {
// let page = obox.clone().to_concrete::<Page>()?; // let page = obox.clone().to_concrete::<Page>()?;
// PostForm::from_page(&page, conn) // PostForm::from_page(&page, conn)
// }) // })
// .map(|pf| upsert_post(&pf?, conn)) // .map(|pf| upsert_post(&pf?, conn))
// .collect::<Result<Vec<Post>, Error>>()?, // .collect::<Result<Vec<Post>, LemmyError>>()?,
// ) // )
// } // }

View file

@ -18,7 +18,10 @@ use crate::{
}, },
convert_datetime, convert_datetime,
db::user::User_, db::user::User_,
request::{retry, RecvError},
routes::webfinger::WebFingerResponse, routes::webfinger::WebFingerResponse,
DbPool,
LemmyError,
MentionData, MentionData,
Settings, Settings,
}; };
@ -28,11 +31,8 @@ use activitystreams::{
}; };
use activitystreams_ext::{Ext1, Ext2, Ext3}; use activitystreams_ext::{Ext1, Ext2, Ext3};
use activitystreams_new::{activity::Follow, object::Tombstone, prelude::*}; use activitystreams_new::{activity::Follow, object::Tombstone, prelude::*};
use actix_web::{body::Body, HttpResponse, Result}; use actix_web::{body::Body, client::Client, HttpResponse};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::PgConnection;
use failure::Error;
use isahc::prelude::*;
use log::debug; use log::debug;
use serde::Serialize; use serde::Serialize;
use url::Url; use url::Url;
@ -101,7 +101,9 @@ pub fn get_apub_protocol_string() -> &'static str {
// Checks if the ID has a valid format, correct scheme, and is in the allowed instance list. // Checks if the ID has a valid format, correct scheme, and is in the allowed instance list.
fn is_apub_id_valid(apub_id: &Url) -> bool { fn is_apub_id_valid(apub_id: &Url) -> bool {
debug!("Checking {}", apub_id);
if apub_id.scheme() != get_apub_protocol_string() { if apub_id.scheme() != get_apub_protocol_string() {
debug!("invalid scheme: {:?}", apub_id.scheme());
return false; return false;
} }
@ -112,15 +114,27 @@ fn is_apub_id_valid(apub_id: &Url) -> bool {
.map(|d| d.to_string()) .map(|d| d.to_string())
.collect(); .collect();
match apub_id.domain() { match apub_id.domain() {
Some(d) => allowed_instances.contains(&d.to_owned()), Some(d) => {
None => false, let contains = allowed_instances.contains(&d.to_owned());
if !contains {
debug!("{} not in {:?}", d, allowed_instances);
}
contains
}
None => {
debug!("missing domain");
false
}
} }
} }
#[async_trait::async_trait(?Send)]
pub trait ToApub { pub trait ToApub {
type Response; type Response;
fn to_apub(&self, conn: &PgConnection) -> Result<Self::Response, Error>; async fn to_apub(&self, pool: &DbPool) -> Result<Self::Response, LemmyError>;
fn to_tombstone(&self) -> Result<Tombstone, Error>; fn to_tombstone(&self) -> Result<Tombstone, LemmyError>;
} }
/// Updated is actually the deletion time /// Updated is actually the deletion time
@ -129,7 +143,7 @@ fn create_tombstone(
object_id: &str, object_id: &str,
updated: Option<NaiveDateTime>, updated: Option<NaiveDateTime>,
former_type: String, former_type: String,
) -> Result<Tombstone, Error> { ) -> Result<Tombstone, LemmyError> {
if deleted { if deleted {
if let Some(updated) = updated { if let Some(updated) = updated {
let mut tombstone = Tombstone::new(); let mut tombstone = Tombstone::new();
@ -138,37 +152,85 @@ fn create_tombstone(
tombstone.set_deleted(convert_datetime(updated).into()); tombstone.set_deleted(convert_datetime(updated).into());
Ok(tombstone) Ok(tombstone)
} else { } else {
Err(format_err!( Err(format_err!("Cant convert to tombstone because updated time was None.").into())
"Cant convert to tombstone because updated time was None."
))
} }
} else { } else {
Err(format_err!( Err(format_err!("Cant convert object to tombstone if it wasnt deleted").into())
"Cant convert object to tombstone if it wasnt deleted"
))
} }
} }
#[async_trait::async_trait(?Send)]
pub trait FromApub { pub trait FromApub {
type ApubType; type ApubType;
fn from_apub(apub: &Self::ApubType, conn: &PgConnection) -> Result<Self, Error> async fn from_apub(
apub: &Self::ApubType,
client: &Client,
pool: &DbPool,
) -> Result<Self, LemmyError>
where where
Self: Sized; Self: Sized;
} }
#[async_trait::async_trait(?Send)]
pub trait ApubObjectType { pub trait ApubObjectType {
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; async fn send_create(
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; &self,
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; creator: &User_,
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; client: &Client,
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>; pool: &DbPool,
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>; ) -> Result<(), LemmyError>;
async fn send_update(
&self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
async fn send_delete(
&self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
async fn send_undo_delete(
&self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
async fn send_remove(
&self,
mod_: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
async fn send_undo_remove(
&self,
mod_: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
} }
#[async_trait::async_trait(?Send)]
pub trait ApubLikeableType { pub trait ApubLikeableType {
fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; async fn send_like(
fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; &self,
fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
async fn send_dislike(
&self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
async fn send_undo_like(
&self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
} }
pub fn get_shared_inbox(actor_id: &str) -> String { pub fn get_shared_inbox(actor_id: &str) -> String {
@ -185,6 +247,7 @@ pub fn get_shared_inbox(actor_id: &str) -> String {
) )
} }
#[async_trait::async_trait(?Send)]
pub trait ActorType { pub trait ActorType {
fn actor_id(&self) -> String; fn actor_id(&self) -> String;
@ -194,20 +257,55 @@ pub trait ActorType {
// These two have default impls, since currently a community can't follow anything, // These two have default impls, since currently a community can't follow anything,
// and a user can't be followed (yet) // and a user can't be followed (yet)
#[allow(unused_variables)] #[allow(unused_variables)]
fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>; async fn send_follow(
fn send_unfollow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>; &self,
follow_actor_id: &str,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
async fn send_unfollow(
&self,
follow_actor_id: &str,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
#[allow(unused_variables)] #[allow(unused_variables)]
fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error>; async fn send_accept_follow(
&self,
follow: &Follow,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; async fn send_delete(
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; &self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
async fn send_undo_delete(
&self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>; async fn send_remove(
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>; &self,
mod_: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
async fn send_undo_remove(
&self,
mod_: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError>;
/// For a given community, returns the inboxes of all followers. /// For a given community, returns the inboxes of all followers.
fn get_follower_inboxes(&self, conn: &PgConnection) -> Result<Vec<String>, Error>; async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<String>, LemmyError>;
// TODO move these to the db rows // TODO move these to the db rows
fn get_inbox_url(&self) -> String { fn get_inbox_url(&self) -> String {
@ -244,7 +342,10 @@ pub trait ActorType {
} }
} }
pub fn fetch_webfinger_url(mention: &MentionData) -> Result<String, Error> { pub async fn fetch_webfinger_url(
mention: &MentionData,
client: &Client,
) -> Result<String, LemmyError> {
let fetch_url = format!( let fetch_url = format!(
"{}://{}/.well-known/webfinger?resource=acct:{}@{}", "{}://{}/.well-known/webfinger?resource=acct:{}@{}",
get_apub_protocol_string(), get_apub_protocol_string(),
@ -253,8 +354,14 @@ pub fn fetch_webfinger_url(mention: &MentionData) -> Result<String, Error> {
mention.domain mention.domain
); );
debug!("Fetching webfinger url: {}", &fetch_url); debug!("Fetching webfinger url: {}", &fetch_url);
let text = isahc::get(&fetch_url)?.text()?;
let res: WebFingerResponse = serde_json::from_str(&text)?; let mut response = retry(|| client.get(&fetch_url).send()).await?;
let res: WebFingerResponse = response
.json()
.await
.map_err(|e| RecvError(e.to_string()))?;
let link = res let link = res
.links .links
.iter() .iter()
@ -263,5 +370,5 @@ pub fn fetch_webfinger_url(mention: &MentionData) -> Result<String, Error> {
link link
.href .href
.to_owned() .to_owned()
.ok_or_else(|| format_err!("No href found.")) .ok_or_else(|| format_err!("No href found.").into())
} }

View file

@ -14,6 +14,7 @@ use crate::{
PageExt, PageExt,
ToApub, ToApub,
}, },
blocking,
convert_datetime, convert_datetime,
db::{ db::{
community::Community, community::Community,
@ -22,6 +23,8 @@ use crate::{
Crud, Crud,
}, },
routes::DbPoolParam, routes::DbPoolParam,
DbPool,
LemmyError,
Settings, Settings,
}; };
use activitystreams::{ use activitystreams::{
@ -32,9 +35,7 @@ use activitystreams::{
}; };
use activitystreams_ext::Ext1; use activitystreams_ext::Ext1;
use activitystreams_new::object::Tombstone; use activitystreams_new::object::Tombstone;
use actix_web::{body::Body, web::Path, HttpResponse, Result}; use actix_web::{body::Body, client::Client, web, HttpResponse};
use diesel::PgConnection;
use failure::Error;
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -44,27 +45,33 @@ pub struct PostQuery {
/// Return the post json over HTTP. /// Return the post json over HTTP.
pub async fn get_apub_post( pub async fn get_apub_post(
info: Path<PostQuery>, info: web::Path<PostQuery>,
db: DbPoolParam, db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> { ) -> Result<HttpResponse<Body>, LemmyError> {
let id = info.post_id.parse::<i32>()?; let id = info.post_id.parse::<i32>()?;
let post = Post::read(&&db.get()?, id)?; let post = blocking(&db, move |conn| Post::read(conn, id)).await??;
if !post.deleted { if !post.deleted {
Ok(create_apub_response(&post.to_apub(&db.get().unwrap())?)) Ok(create_apub_response(&post.to_apub(&db).await?))
} else { } else {
Ok(create_apub_tombstone_response(&post.to_tombstone()?)) Ok(create_apub_tombstone_response(&post.to_tombstone()?))
} }
} }
#[async_trait::async_trait(?Send)]
impl ToApub for Post { impl ToApub for Post {
type Response = PageExt; type Response = PageExt;
// Turn a Lemmy post into an ActivityPub page that can be sent out over the network. // Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
fn to_apub(&self, conn: &PgConnection) -> Result<PageExt, Error> { async fn to_apub(&self, pool: &DbPool) -> Result<PageExt, LemmyError> {
let mut page = Page::default(); let mut page = Page::default();
let oprops: &mut ObjectProperties = page.as_mut(); let oprops: &mut ObjectProperties = page.as_mut();
let creator = User_::read(conn, self.creator_id)?;
let community = Community::read(conn, self.community_id)?; let creator_id = self.creator_id;
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
oprops oprops
// Not needed when the Post is embedded in a collection (like for community outbox) // Not needed when the Post is embedded in a collection (like for community outbox)
@ -141,7 +148,7 @@ impl ToApub for Post {
Ok(Ext1::new(page, ext)) Ok(Ext1::new(page, ext))
} }
fn to_tombstone(&self) -> Result<Tombstone, Error> { fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
create_tombstone( create_tombstone(
self.deleted, self.deleted,
&self.ap_id, &self.ap_id,
@ -151,17 +158,26 @@ impl ToApub for Post {
} }
} }
#[async_trait::async_trait(?Send)]
impl FromApub for PostForm { impl FromApub for PostForm {
type ApubType = PageExt; type ApubType = PageExt;
/// Parse an ActivityPub page received from another instance into a Lemmy post. /// Parse an ActivityPub page received from another instance into a Lemmy post.
fn from_apub(page: &PageExt, conn: &PgConnection) -> Result<PostForm, Error> { async fn from_apub(
page: &PageExt,
client: &Client,
pool: &DbPool,
) -> Result<PostForm, LemmyError> {
let ext = &page.ext_one; let ext = &page.ext_one;
let oprops = &page.inner.object_props; let oprops = &page.inner.object_props;
let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string(); let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?;
let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, client, pool).await?;
let community_actor_id = &oprops.get_to_xsd_any_uri().unwrap().to_string(); let community_actor_id = &oprops.get_to_xsd_any_uri().unwrap().to_string();
let community = get_or_fetch_and_upsert_remote_community(&community_actor_id, &conn)?;
let community =
get_or_fetch_and_upsert_remote_community(&community_actor_id, client, pool).await?;
let thumbnail_url = match oprops.get_image_any_image() { let thumbnail_url = match oprops.get_image_any_image() {
Some(any_image) => any_image Some(any_image) => any_image
@ -221,11 +237,20 @@ impl FromApub for PostForm {
} }
} }
#[async_trait::async_trait(?Send)]
impl ApubObjectType for Post { 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.
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_create(
let page = self.to_apub(conn)?; &self,
let community = Community::read(conn, self.community_id)?; creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let page = self.to_apub(pool).await?;
let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
let mut create = Create::new(); let mut create = Create::new();
@ -241,18 +266,28 @@ impl ApubObjectType for Post {
send_activity_to_community( send_activity_to_community(
creator, creator,
conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
create, create,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
/// Send out information about an edited post, to the followers of the community. /// Send out information about an edited post, to the followers of the community.
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_update(
let page = self.to_apub(conn)?; &self,
let community = Community::read(conn, self.community_id)?; creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let page = self.to_apub(pool).await?;
let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
let mut update = Update::new(); let mut update = Update::new();
@ -268,17 +303,27 @@ impl ApubObjectType for Post {
send_activity_to_community( send_activity_to_community(
creator, creator,
conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
update, update,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_delete(
let page = self.to_apub(conn)?; &self,
let community = Community::read(conn, self.community_id)?; creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let page = self.to_apub(pool).await?;
let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
let mut delete = Delete::default(); let mut delete = Delete::default();
@ -293,21 +338,29 @@ impl ApubObjectType for Post {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(BaseBox::from_concrete(page)?)?; .set_object_base_box(BaseBox::from_concrete(page)?)?;
let community = Community::read(conn, self.community_id)?;
send_activity_to_community( send_activity_to_community(
creator, creator,
conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
delete, delete,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_undo_delete(
let page = self.to_apub(conn)?; &self,
let community = Community::read(conn, self.community_id)?; creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let page = self.to_apub(pool).await?;
let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
let mut delete = Delete::default(); let mut delete = Delete::default();
@ -338,20 +391,29 @@ impl ApubObjectType for Post {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(delete)?; .set_object_base_box(delete)?;
let community = Community::read(conn, self.community_id)?;
send_activity_to_community( send_activity_to_community(
creator, creator,
conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
undo, undo,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_remove(
let page = self.to_apub(conn)?; &self,
let community = Community::read(conn, self.community_id)?; mod_: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let page = self.to_apub(pool).await?;
let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
let mut remove = Remove::default(); let mut remove = Remove::default();
@ -366,20 +428,29 @@ impl ApubObjectType for Post {
.set_actor_xsd_any_uri(mod_.actor_id.to_owned())? .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
.set_object_base_box(BaseBox::from_concrete(page)?)?; .set_object_base_box(BaseBox::from_concrete(page)?)?;
let community = Community::read(conn, self.community_id)?;
send_activity_to_community( send_activity_to_community(
mod_, mod_,
conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
remove, remove,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = self.to_apub(conn)?; async fn send_undo_remove(
let community = Community::read(conn, self.community_id)?; &self,
mod_: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let page = self.to_apub(pool).await?;
let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
let mut remove = Remove::default(); let mut remove = Remove::default();
@ -409,22 +480,32 @@ impl ApubObjectType for Post {
.set_actor_xsd_any_uri(mod_.actor_id.to_owned())? .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
.set_object_base_box(remove)?; .set_object_base_box(remove)?;
let community = Community::read(conn, self.community_id)?;
send_activity_to_community( send_activity_to_community(
mod_, mod_,
conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
undo, undo,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
} }
#[async_trait::async_trait(?Send)]
impl ApubLikeableType for Post { impl ApubLikeableType for Post {
fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_like(
let page = self.to_apub(conn)?; &self,
let community = Community::read(conn, self.community_id)?; creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let page = self.to_apub(pool).await?;
let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
let mut like = Like::new(); let mut like = Like::new();
@ -440,17 +521,27 @@ impl ApubLikeableType for Post {
send_activity_to_community( send_activity_to_community(
&creator, &creator,
&conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
like, like,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_dislike(
let page = self.to_apub(conn)?; &self,
let community = Community::read(conn, self.community_id)?; creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let page = self.to_apub(pool).await?;
let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
let mut dislike = Dislike::new(); let mut dislike = Dislike::new();
@ -466,17 +557,27 @@ impl ApubLikeableType for Post {
send_activity_to_community( send_activity_to_community(
&creator, &creator,
&conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
dislike, dislike,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_undo_like(
let page = self.to_apub(conn)?; &self,
let community = Community::read(conn, self.community_id)?; creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let page = self.to_apub(pool).await?;
let community_id = self.community_id;
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
let mut like = Like::new(); let mut like = Like::new();
@ -508,11 +609,13 @@ impl ApubLikeableType for Post {
send_activity_to_community( send_activity_to_community(
&creator, &creator,
&conn,
&community, &community,
vec![community.get_shared_inbox_url()], vec![community.get_shared_inbox_url()],
undo, undo,
)?; client,
pool,
)
.await?;
Ok(()) Ok(())
} }
} }

View file

@ -7,6 +7,7 @@ use crate::{
FromApub, FromApub,
ToApub, ToApub,
}, },
blocking,
convert_datetime, convert_datetime,
db::{ db::{
activity::insert_activity, activity::insert_activity,
@ -14,6 +15,8 @@ use crate::{
user::User_, user::User_,
Crud, Crud,
}, },
DbPool,
LemmyError,
}; };
use activitystreams::{ use activitystreams::{
activity::{Create, Delete, Undo, Update}, activity::{Create, Delete, Undo, Update},
@ -21,18 +24,21 @@ use activitystreams::{
object::{kind::NoteType, properties::ObjectProperties, Note}, object::{kind::NoteType, properties::ObjectProperties, Note},
}; };
use activitystreams_new::object::Tombstone; use activitystreams_new::object::Tombstone;
use actix_web::Result; use actix_web::client::Client;
use diesel::PgConnection;
use failure::Error;
#[async_trait::async_trait(?Send)]
impl ToApub for PrivateMessage { impl ToApub for PrivateMessage {
type Response = Note; type Response = Note;
fn to_apub(&self, conn: &PgConnection) -> Result<Note, Error> { async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
let mut private_message = Note::default(); let mut private_message = Note::default();
let oprops: &mut ObjectProperties = private_message.as_mut(); let oprops: &mut ObjectProperties = private_message.as_mut();
let creator = User_::read(&conn, self.creator_id)?;
let recipient = User_::read(&conn, self.recipient_id)?; let creator_id = self.creator_id;
let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??;
let recipient_id = self.recipient_id;
let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
oprops oprops
.set_context_xsd_any_uri(context())? .set_context_xsd_any_uri(context())?
@ -49,7 +55,7 @@ impl ToApub for PrivateMessage {
Ok(private_message) Ok(private_message)
} }
fn to_tombstone(&self) -> Result<Tombstone, Error> { fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
create_tombstone( create_tombstone(
self.deleted, self.deleted,
&self.ap_id, &self.ap_id,
@ -59,16 +65,24 @@ impl ToApub for PrivateMessage {
} }
} }
#[async_trait::async_trait(?Send)]
impl FromApub for PrivateMessageForm { impl FromApub for PrivateMessageForm {
type ApubType = Note; type ApubType = Note;
/// Parse an ActivityPub note received from another instance into a Lemmy Private message /// Parse an ActivityPub note received from another instance into a Lemmy Private message
fn from_apub(note: &Note, conn: &PgConnection) -> Result<PrivateMessageForm, Error> { async fn from_apub(
note: &Note,
client: &Client,
pool: &DbPool,
) -> Result<PrivateMessageForm, LemmyError> {
let oprops = &note.object_props; let oprops = &note.object_props;
let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string(); let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?;
let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, client, pool).await?;
let recipient_actor_id = &oprops.get_to_xsd_any_uri().unwrap().to_string(); let recipient_actor_id = &oprops.get_to_xsd_any_uri().unwrap().to_string();
let recipient = get_or_fetch_and_upsert_remote_user(&recipient_actor_id, &conn)?;
let recipient = get_or_fetch_and_upsert_remote_user(&recipient_actor_id, client, pool).await?;
Ok(PrivateMessageForm { Ok(PrivateMessageForm {
creator_id: creator.id, creator_id: creator.id,
@ -91,12 +105,20 @@ impl FromApub for PrivateMessageForm {
} }
} }
#[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
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_create(
let note = self.to_apub(conn)?; &self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
let recipient = User_::read(&conn, self.recipient_id)?;
let recipient_id = self.recipient_id;
let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
let mut create = Create::new(); let mut create = Create::new();
create create
@ -110,17 +132,24 @@ impl ApubObjectType for PrivateMessage {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(note)?; .set_object_base_box(note)?;
insert_activity(&conn, creator.id, &create, true)?; insert_activity(creator.id, create.clone(), true, pool).await?;
send_activity(&create, creator, vec![to])?; send_activity(client, &create, creator, vec![to]).await?;
Ok(()) Ok(())
} }
/// Send out information about an edited post, to the followers of the community. /// Send out information about an edited post, to the followers of the community.
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_update(
let note = self.to_apub(conn)?; &self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
let recipient = User_::read(&conn, self.recipient_id)?;
let recipient_id = self.recipient_id;
let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
let mut update = Update::new(); let mut update = Update::new();
update update
@ -134,16 +163,23 @@ impl ApubObjectType for PrivateMessage {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(note)?; .set_object_base_box(note)?;
insert_activity(&conn, creator.id, &update, true)?; insert_activity(creator.id, update.clone(), true, pool).await?;
send_activity(&update, creator, vec![to])?; send_activity(client, &update, creator, vec![to]).await?;
Ok(()) Ok(())
} }
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_delete(
let note = self.to_apub(conn)?; &self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
let recipient = User_::read(&conn, self.recipient_id)?;
let recipient_id = self.recipient_id;
let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
let mut delete = Delete::new(); let mut delete = Delete::new();
delete delete
@ -157,16 +193,23 @@ impl ApubObjectType for PrivateMessage {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(note)?; .set_object_base_box(note)?;
insert_activity(&conn, creator.id, &delete, true)?; insert_activity(creator.id, delete.clone(), true, pool).await?;
send_activity(&delete, creator, vec![to])?; send_activity(client, &delete, creator, vec![to]).await?;
Ok(()) Ok(())
} }
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { async fn send_undo_delete(
let note = self.to_apub(conn)?; &self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let note = self.to_apub(pool).await?;
let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4()); let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
let recipient = User_::read(&conn, self.recipient_id)?;
let recipient_id = self.recipient_id;
let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??;
let mut delete = Delete::new(); let mut delete = Delete::new();
delete delete
@ -195,17 +238,27 @@ impl ApubObjectType for PrivateMessage {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())? .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(delete)?; .set_object_base_box(delete)?;
insert_activity(&conn, creator.id, &undo, true)?; insert_activity(creator.id, undo.clone(), true, pool).await?;
send_activity(&undo, creator, vec![to])?; send_activity(client, &undo, creator, vec![to]).await?;
Ok(()) Ok(())
} }
fn send_remove(&self, _mod_: &User_, _conn: &PgConnection) -> Result<(), Error> { async fn send_remove(
&self,
_mod_: &User_,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
unimplemented!() unimplemented!()
} }
fn send_undo_remove(&self, _mod_: &User_, _conn: &PgConnection) -> Result<(), Error> { async fn send_undo_remove(
&self,
_mod_: &User_,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
unimplemented!() unimplemented!()
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@ use crate::{
PersonExt, PersonExt,
ToApub, ToApub,
}, },
blocking,
convert_datetime, convert_datetime,
db::{ db::{
activity::insert_activity, activity::insert_activity,
@ -15,6 +16,8 @@ use crate::{
}, },
naive_now, naive_now,
routes::DbPoolParam, routes::DbPoolParam,
DbPool,
LemmyError,
}; };
use activitystreams::{ use activitystreams::{
actor::{properties::ApActorProperties, Person}, actor::{properties::ApActorProperties, Person},
@ -29,9 +32,7 @@ use activitystreams_new::{
object::Tombstone, object::Tombstone,
prelude::*, prelude::*,
}; };
use actix_web::{body::Body, web::Path, HttpResponse, Result}; use actix_web::{body::Body, client::Client, web, HttpResponse};
use diesel::PgConnection;
use failure::Error;
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -39,11 +40,12 @@ pub struct UserQuery {
user_name: String, user_name: String,
} }
#[async_trait::async_trait(?Send)]
impl ToApub for User_ { impl ToApub for User_ {
type Response = PersonExt; type Response = PersonExt;
// Turn a Lemmy Community into an ActivityPub group that can be sent out over the network. // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
fn to_apub(&self, _conn: &PgConnection) -> Result<PersonExt, Error> { async fn to_apub(&self, _pool: &DbPool) -> Result<PersonExt, LemmyError> {
// TODO go through all these to_string and to_owned() // TODO go through all these to_string and to_owned()
let mut person = Person::default(); let mut person = Person::default();
let oprops: &mut ObjectProperties = person.as_mut(); let oprops: &mut ObjectProperties = person.as_mut();
@ -86,11 +88,12 @@ impl ToApub for User_ {
Ok(Ext2::new(person, actor_props, self.get_public_key_ext())) Ok(Ext2::new(person, actor_props, self.get_public_key_ext()))
} }
fn to_tombstone(&self) -> Result<Tombstone, Error> { fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
unimplemented!() unimplemented!()
} }
} }
#[async_trait::async_trait(?Send)]
impl ActorType for User_ { impl ActorType for User_ {
fn actor_id(&self) -> String { fn actor_id(&self) -> String {
self.actor_id.to_owned() self.actor_id.to_owned()
@ -105,19 +108,29 @@ impl ActorType for User_ {
} }
/// As a given local user, send out a follow request to a remote community. /// As a given local user, send out a follow request to a remote community.
fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> { async fn send_follow(
&self,
follow_actor_id: &str,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4()); let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id); let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
follow.set_context(context()).set_id(id.parse()?); follow.set_context(context()).set_id(id.parse()?);
let to = format!("{}/inbox", follow_actor_id); let to = format!("{}/inbox", follow_actor_id);
insert_activity(&conn, self.id, &follow, true)?; insert_activity(self.id, follow.clone(), true, pool).await?;
send_activity(&follow, self, vec![to])?; send_activity(client, &follow, self, vec![to]).await?;
Ok(()) Ok(())
} }
fn send_unfollow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> { async fn send_unfollow(
&self,
follow_actor_id: &str,
client: &Client,
pool: &DbPool,
) -> Result<(), LemmyError> {
let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4()); let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id); let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
follow.set_context(context()).set_id(id.parse()?); follow.set_context(context()).set_id(id.parse()?);
@ -130,41 +143,67 @@ impl ActorType for User_ {
let mut undo = Undo::new(self.actor_id.parse::<XsdAnyUri>()?, follow.into_any_base()?); let mut undo = Undo::new(self.actor_id.parse::<XsdAnyUri>()?, follow.into_any_base()?);
undo.set_context(context()).set_id(undo_id.parse()?); undo.set_context(context()).set_id(undo_id.parse()?);
insert_activity(&conn, self.id, &undo, true)?; insert_activity(self.id, undo.clone(), true, pool).await?;
send_activity(&undo, self, vec![to])?; send_activity(client, &undo, self, vec![to]).await?;
Ok(()) Ok(())
} }
fn send_delete(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> { async fn send_delete(
&self,
_creator: &User_,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
unimplemented!() unimplemented!()
} }
fn send_undo_delete(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> { async fn send_undo_delete(
&self,
_creator: &User_,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
unimplemented!() unimplemented!()
} }
fn send_remove(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> { async fn send_remove(
&self,
_creator: &User_,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
unimplemented!() unimplemented!()
} }
fn send_undo_remove(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> { async fn send_undo_remove(
&self,
_creator: &User_,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
unimplemented!() unimplemented!()
} }
fn send_accept_follow(&self, _follow: &Follow, _conn: &PgConnection) -> Result<(), Error> { async fn send_accept_follow(
&self,
_follow: &Follow,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
unimplemented!() unimplemented!()
} }
fn get_follower_inboxes(&self, _conn: &PgConnection) -> Result<Vec<String>, Error> { async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<String>, LemmyError> {
unimplemented!() unimplemented!()
} }
} }
#[async_trait::async_trait(?Send)]
impl FromApub for UserForm { impl FromApub for UserForm {
type ApubType = PersonExt; type ApubType = PersonExt;
/// Parse an ActivityPub person received from another instance into a Lemmy user. /// Parse an ActivityPub person received from another instance into a Lemmy user.
fn from_apub(person: &PersonExt, _conn: &PgConnection) -> Result<Self, Error> { async fn from_apub(person: &PersonExt, _: &Client, _: &DbPool) -> Result<Self, LemmyError> {
let oprops = &person.inner.object_props; let oprops = &person.inner.object_props;
let aprops = &person.ext_one; let aprops = &person.ext_one;
let public_key: &PublicKey = &person.ext_two.public_key; let public_key: &PublicKey = &person.ext_two.public_key;
@ -210,10 +249,14 @@ impl FromApub for UserForm {
/// Return the user json over HTTP. /// Return the user json over HTTP.
pub async fn get_apub_user_http( pub async fn get_apub_user_http(
info: Path<UserQuery>, info: web::Path<UserQuery>,
db: DbPoolParam, db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> { ) -> Result<HttpResponse<Body>, LemmyError> {
let user = User_::find_by_email_or_username(&&db.get()?, &info.user_name)?; let user_name = info.into_inner().user_name;
let u = user.to_apub(&db.get().unwrap())?; let user = blocking(&db, move |conn| {
User_::find_by_email_or_username(conn, &user_name)
})
.await??;
let u = user.to_apub(&db).await?;
Ok(create_apub_response(&u)) Ok(create_apub_response(&u))
} }

View file

@ -5,6 +5,7 @@ use crate::{
fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user}, fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
FromApub, FromApub,
}, },
blocking,
db::{ db::{
activity::insert_activity, activity::insert_activity,
community::{CommunityFollower, CommunityFollowerForm}, community::{CommunityFollower, CommunityFollowerForm},
@ -17,16 +18,17 @@ use crate::{
naive_now, naive_now,
routes::{ChatServerParam, DbPoolParam}, routes::{ChatServerParam, DbPoolParam},
websocket::{server::SendUserRoomMessage, UserOperation}, websocket::{server::SendUserRoomMessage, UserOperation},
DbPool,
LemmyError,
}; };
use activitystreams::{ use activitystreams::{
activity::{Accept, Create, Delete, Undo, Update}, activity::{Accept, Create, Delete, Undo, Update},
object::Note, object::Note,
}; };
use actix_web::{web, HttpRequest, HttpResponse, Result}; use actix_web::{client::Client, web, HttpRequest, HttpResponse};
use diesel::PgConnection;
use failure::{Error, _core::fmt::Debug};
use log::debug; use log::debug;
use serde::Deserialize; use serde::Deserialize;
use std::fmt::Debug;
#[serde(untagged)] #[serde(untagged)]
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@ -43,51 +45,53 @@ pub async fn user_inbox(
request: HttpRequest, request: HttpRequest,
input: web::Json<UserAcceptedObjects>, input: web::Json<UserAcceptedObjects>,
path: web::Path<String>, path: web::Path<String>,
client: web::Data<Client>,
db: DbPoolParam, db: DbPoolParam,
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, LemmyError> {
// TODO: would be nice if we could do the signature check here, but we cant access the actor property // TODO: would be nice if we could do the signature check here, but we cant access the actor property
let input = input.into_inner(); let input = input.into_inner();
let conn = &db.get().unwrap();
let username = path.into_inner(); let username = path.into_inner();
debug!("User {} received activity: {:?}", &username, &input); debug!("User {} received activity: {:?}", &username, &input);
match input { match input {
UserAcceptedObjects::Accept(a) => receive_accept(&a, &request, &username, &conn), UserAcceptedObjects::Accept(a) => receive_accept(*a, &request, &username, &client, &db).await,
UserAcceptedObjects::Create(c) => { UserAcceptedObjects::Create(c) => {
receive_create_private_message(&c, &request, &conn, chat_server) receive_create_private_message(*c, &request, &client, &db, chat_server).await
} }
UserAcceptedObjects::Update(u) => { UserAcceptedObjects::Update(u) => {
receive_update_private_message(&u, &request, &conn, chat_server) receive_update_private_message(*u, &request, &client, &db, chat_server).await
} }
UserAcceptedObjects::Delete(d) => { UserAcceptedObjects::Delete(d) => {
receive_delete_private_message(&d, &request, &conn, chat_server) receive_delete_private_message(*d, &request, &client, &db, chat_server).await
} }
UserAcceptedObjects::Undo(u) => { UserAcceptedObjects::Undo(u) => {
receive_undo_delete_private_message(&u, &request, &conn, chat_server) receive_undo_delete_private_message(*u, &request, &client, &db, chat_server).await
} }
} }
} }
/// Handle accepted follows. /// Handle accepted follows.
fn receive_accept( async fn receive_accept(
accept: &Accept, accept: Accept,
request: &HttpRequest, request: &HttpRequest,
username: &str, username: &str,
conn: &PgConnection, client: &Client,
) -> Result<HttpResponse, Error> { pool: &DbPool,
) -> Result<HttpResponse, LemmyError> {
let community_uri = accept let community_uri = accept
.accept_props .accept_props
.get_actor_xsd_any_uri() .get_actor_xsd_any_uri()
.unwrap() .unwrap()
.to_string(); .to_string();
let community = get_or_fetch_and_upsert_remote_community(&community_uri, conn)?; let community = get_or_fetch_and_upsert_remote_community(&community_uri, client, pool).await?;
verify(request, &community)?; verify(request, &community)?;
let user = User_::read_from_name(&conn, username)?; let username = username.to_owned();
let user = blocking(pool, move |conn| User_::read_from_name(conn, &username)).await??;
insert_activity(&conn, community.creator_id, &accept, false)?; insert_activity(community.creator_id, accept, false, pool).await?;
// Now you need to add this to the community follower // Now you need to add this to the community follower
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
@ -96,18 +100,22 @@ fn receive_accept(
}; };
// This will fail if they're already a follower // This will fail if they're already a follower
CommunityFollower::follow(&conn, &community_follower_form)?; blocking(pool, move |conn| {
CommunityFollower::follow(conn, &community_follower_form)
})
.await??;
// TODO: make sure that we actually requested a follow // TODO: make sure that we actually requested a follow
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
fn receive_create_private_message( async fn receive_create_private_message(
create: &Create, create: Create,
request: &HttpRequest, request: &HttpRequest,
conn: &PgConnection, client: &Client,
pool: &DbPool,
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, LemmyError> {
let note = create let note = create
.create_props .create_props
.get_object_base_box() .get_object_base_box()
@ -122,36 +130,44 @@ fn receive_create_private_message(
.unwrap() .unwrap()
.to_string(); .to_string();
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
verify(request, &user)?; verify(request, &user)?;
insert_activity(&conn, user.id, &create, false)?; insert_activity(user.id, create, false, pool).await?;
let private_message = PrivateMessageForm::from_apub(&note, &conn)?; let private_message = PrivateMessageForm::from_apub(&note, client, pool).await?;
let inserted_private_message = PrivateMessage::create(&conn, &private_message)?;
let message = PrivateMessageView::read(&conn, inserted_private_message.id)?; let inserted_private_message = blocking(pool, move |conn| {
PrivateMessage::create(conn, &private_message)
})
.await??;
let res = PrivateMessageResponse { let message = blocking(pool, move |conn| {
message: message.to_owned(), PrivateMessageView::read(conn, inserted_private_message.id)
}; })
.await??;
let res = PrivateMessageResponse { message };
let recipient_id = res.message.recipient_id;
chat_server.do_send(SendUserRoomMessage { chat_server.do_send(SendUserRoomMessage {
op: UserOperation::CreatePrivateMessage, op: UserOperation::CreatePrivateMessage,
response: res, response: res,
recipient_id: message.recipient_id, recipient_id,
my_id: None, my_id: None,
}); });
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
fn receive_update_private_message( async fn receive_update_private_message(
update: &Update, update: Update,
request: &HttpRequest, request: &HttpRequest,
conn: &PgConnection, client: &Client,
pool: &DbPool,
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, LemmyError> {
let note = update let note = update
.update_props .update_props
.get_object_base_box() .get_object_base_box()
@ -166,37 +182,52 @@ fn receive_update_private_message(
.unwrap() .unwrap()
.to_string(); .to_string();
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
verify(request, &user)?; verify(request, &user)?;
insert_activity(&conn, user.id, &update, false)?; insert_activity(user.id, update, false, pool).await?;
let private_message = PrivateMessageForm::from_apub(&note, &conn)?; let private_message_form = PrivateMessageForm::from_apub(&note, client, pool).await?;
let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id;
PrivateMessage::update(conn, private_message_id, &private_message)?;
let message = PrivateMessageView::read(&conn, private_message_id)?; let private_message_ap_id = private_message_form.ap_id.clone();
let private_message = blocking(pool, move |conn| {
PrivateMessage::read_from_apub_id(conn, &private_message_ap_id)
})
.await??;
let res = PrivateMessageResponse { let private_message_id = private_message.id;
message: message.to_owned(), blocking(pool, move |conn| {
}; PrivateMessage::update(conn, private_message_id, &private_message_form)
})
.await??;
let private_message_id = private_message.id;
let message = blocking(pool, move |conn| {
PrivateMessageView::read(conn, private_message_id)
})
.await??;
let res = PrivateMessageResponse { message };
let recipient_id = res.message.recipient_id;
chat_server.do_send(SendUserRoomMessage { chat_server.do_send(SendUserRoomMessage {
op: UserOperation::EditPrivateMessage, op: UserOperation::EditPrivateMessage,
response: res, response: res,
recipient_id: message.recipient_id, recipient_id,
my_id: None, my_id: None,
}); });
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
fn receive_delete_private_message( async fn receive_delete_private_message(
delete: &Delete, delete: Delete,
request: &HttpRequest, request: &HttpRequest,
conn: &PgConnection, client: &Client,
pool: &DbPool,
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, LemmyError> {
let note = delete let note = delete
.delete_props .delete_props
.get_object_base_box() .get_object_base_box()
@ -211,15 +242,21 @@ fn receive_delete_private_message(
.unwrap() .unwrap()
.to_string(); .to_string();
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
verify(request, &user)?; verify(request, &user)?;
insert_activity(&conn, user.id, &delete, false)?; insert_activity(user.id, delete, false, pool).await?;
let private_message_form = PrivateMessageForm::from_apub(&note, client, pool).await?;
let private_message_ap_id = private_message_form.ap_id;
let private_message = blocking(pool, move |conn| {
PrivateMessage::read_from_apub_id(conn, &private_message_ap_id)
})
.await??;
let private_message = PrivateMessageForm::from_apub(&note, &conn)?;
let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id;
let private_message_form = PrivateMessageForm { let private_message_form = PrivateMessageForm {
content: private_message.content, content: private_message_form.content,
recipient_id: private_message.recipient_id, recipient_id: private_message.recipient_id,
creator_id: private_message.creator_id, creator_id: private_message.creator_id,
deleted: Some(true), deleted: Some(true),
@ -229,30 +266,40 @@ fn receive_delete_private_message(
published: None, published: None,
updated: Some(naive_now()), updated: Some(naive_now()),
}; };
PrivateMessage::update(conn, private_message_id, &private_message_form)?;
let message = PrivateMessageView::read(&conn, private_message_id)?; let private_message_id = private_message.id;
blocking(pool, move |conn| {
PrivateMessage::update(conn, private_message_id, &private_message_form)
})
.await??;
let res = PrivateMessageResponse { let private_message_id = private_message.id;
message: message.to_owned(), let message = blocking(pool, move |conn| {
}; PrivateMessageView::read(&conn, private_message_id)
})
.await??;
let res = PrivateMessageResponse { message };
let recipient_id = res.message.recipient_id;
chat_server.do_send(SendUserRoomMessage { chat_server.do_send(SendUserRoomMessage {
op: UserOperation::EditPrivateMessage, op: UserOperation::EditPrivateMessage,
response: res, response: res,
recipient_id: message.recipient_id, recipient_id,
my_id: None, my_id: None,
}); });
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
fn receive_undo_delete_private_message( async fn receive_undo_delete_private_message(
undo: &Undo, undo: Undo,
request: &HttpRequest, request: &HttpRequest,
conn: &PgConnection, client: &Client,
pool: &DbPool,
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, LemmyError> {
let delete = undo let delete = undo
.undo_props .undo_props
.get_object_base_box() .get_object_base_box()
@ -275,13 +322,19 @@ fn receive_undo_delete_private_message(
.unwrap() .unwrap()
.to_string(); .to_string();
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; let user = get_or_fetch_and_upsert_remote_user(&user_uri, client, pool).await?;
verify(request, &user)?; verify(request, &user)?;
insert_activity(&conn, user.id, &delete, false)?; insert_activity(user.id, delete, false, pool).await?;
let private_message = PrivateMessageForm::from_apub(&note, client, pool).await?;
let private_message_ap_id = private_message.ap_id.clone();
let private_message_id = blocking(pool, move |conn| {
PrivateMessage::read_from_apub_id(conn, &private_message_ap_id).map(|pm| pm.id)
})
.await??;
let private_message = PrivateMessageForm::from_apub(&note, &conn)?;
let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id;
let private_message_form = PrivateMessageForm { let private_message_form = PrivateMessageForm {
content: private_message.content, content: private_message.content,
recipient_id: private_message.recipient_id, recipient_id: private_message.recipient_id,
@ -293,18 +346,25 @@ fn receive_undo_delete_private_message(
published: None, published: None,
updated: Some(naive_now()), updated: Some(naive_now()),
}; };
PrivateMessage::update(conn, private_message_id, &private_message_form)?;
let message = PrivateMessageView::read(&conn, private_message_id)?; blocking(pool, move |conn| {
PrivateMessage::update(conn, private_message_id, &private_message_form)
})
.await??;
let res = PrivateMessageResponse { let message = blocking(pool, move |conn| {
message: message.to_owned(), PrivateMessageView::read(&conn, private_message_id)
}; })
.await??;
let res = PrivateMessageResponse { message };
let recipient_id = res.message.recipient_id;
chat_server.do_send(SendUserRoomMessage { chat_server.do_send(SendUserRoomMessage {
op: UserOperation::EditPrivateMessage, op: UserOperation::EditPrivateMessage,
response: res, response: res,
recipient_id: message.recipient_id, recipient_id,
my_id: None, my_id: None,
}); });

View file

@ -1,9 +1,9 @@
use crate::{db::Crud, schema::activity}; use crate::{blocking, db::Crud, schema::activity, DbPool, LemmyError};
use diesel::{dsl::*, result::Error, *}; use diesel::{dsl::*, result::Error, *};
use failure::_core::fmt::Debug;
use log::debug; use log::debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use std::fmt::Debug;
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "activity"] #[table_name = "activity"]
@ -55,12 +55,28 @@ impl Crud<ActivityForm> for Activity {
} }
} }
pub fn insert_activity<T>( pub async fn insert_activity<T>(
user_id: i32,
data: T,
local: bool,
pool: &DbPool,
) -> Result<(), LemmyError>
where
T: Serialize + Debug + Send + 'static,
{
blocking(pool, move |conn| {
do_insert_activity(conn, user_id, &data, local)
})
.await??;
Ok(())
}
fn do_insert_activity<T>(
conn: &PgConnection, conn: &PgConnection,
user_id: i32, user_id: i32,
data: &T, data: &T,
local: bool, local: bool,
) -> Result<(), failure::Error> ) -> Result<(), LemmyError>
where where
T: Serialize + Debug, T: Serialize + Debug,
{ {

View file

@ -10,21 +10,22 @@ use crate::{
apub::{extensions::signatures::generate_actor_keypair, make_apub_endpoint, EndpointType}, apub::{extensions::signatures::generate_actor_keypair, make_apub_endpoint, EndpointType},
db::Crud, db::Crud,
naive_now, naive_now,
LemmyError,
}; };
use diesel::*; use diesel::*;
use failure::Error;
use log::info; use log::info;
pub fn run_advanced_migrations(conn: &PgConnection) -> Result<(), Error> { pub fn run_advanced_migrations(conn: &PgConnection) -> Result<(), LemmyError> {
user_updates_2020_04_02(&conn)?; user_updates_2020_04_02(&conn)?;
community_updates_2020_04_02(&conn)?; community_updates_2020_04_02(&conn)?;
post_updates_2020_04_03(&conn)?; post_updates_2020_04_03(&conn)?;
comment_updates_2020_04_03(&conn)?; comment_updates_2020_04_03(&conn)?;
private_message_updates_2020_05_05(&conn)?; private_message_updates_2020_05_05(&conn)?;
Ok(()) Ok(())
} }
fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> { fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> {
use crate::schema::user_::dsl::*; use crate::schema::user_::dsl::*;
info!("Running user_updates_2020_04_02"); info!("Running user_updates_2020_04_02");
@ -75,7 +76,7 @@ fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> { fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> {
use crate::schema::community::dsl::*; use crate::schema::community::dsl::*;
info!("Running community_updates_2020_04_02"); info!("Running community_updates_2020_04_02");
@ -119,7 +120,7 @@ fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> { fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> {
use crate::schema::post::dsl::*; use crate::schema::post::dsl::*;
info!("Running post_updates_2020_04_03"); info!("Running post_updates_2020_04_03");
@ -143,7 +144,7 @@ fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> { fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> {
use crate::schema::comment::dsl::*; use crate::schema::comment::dsl::*;
info!("Running comment_updates_2020_04_03"); info!("Running comment_updates_2020_04_03");
@ -167,7 +168,7 @@ fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), Error> { fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), LemmyError> {
use crate::schema::private_message::dsl::*; use crate::schema::private_message::dsl::*;
info!("Running private_message_updates_2020_05_05"); info!("Running private_message_updates_2020_05_05");

View file

@ -12,7 +12,7 @@ use crate::{
// ) // )
// SELECT * FROM MyTree; // SELECT * FROM MyTree;
#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[belongs_to(Post)] #[belongs_to(Post)]
#[table_name = "comment"] #[table_name = "comment"]
pub struct Comment { pub struct Comment {

View file

@ -5,7 +5,7 @@ use crate::{
use diesel::{dsl::*, result::Error, *}; use diesel::{dsl::*, result::Error, *};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] #[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "community"] #[table_name = "community"]
pub struct Community { pub struct Community {
pub id: i32, pub id: i32,

View file

@ -50,8 +50,8 @@ impl Crud<PasswordResetRequestForm> for PasswordResetRequest {
impl PasswordResetRequest { impl PasswordResetRequest {
pub fn create_token(conn: &PgConnection, from_user_id: i32, token: &str) -> Result<Self, Error> { pub fn create_token(conn: &PgConnection, from_user_id: i32, token: &str) -> Result<Self, Error> {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.input(token); hasher.update(token);
let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.result().to_vec()); let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.finalize().to_vec());
let form = PasswordResetRequestForm { let form = PasswordResetRequestForm {
user_id: from_user_id, user_id: from_user_id,
@ -62,8 +62,8 @@ impl PasswordResetRequest {
} }
pub fn read_from_token(conn: &PgConnection, token: &str) -> Result<Self, Error> { pub fn read_from_token(conn: &PgConnection, token: &str) -> Result<Self, Error> {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.input(token); hasher.update(token);
let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.result().to_vec()); let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.finalize().to_vec());
password_reset_request password_reset_request
.filter(token_encrypted.eq(token_hash)) .filter(token_encrypted.eq(token_hash))
.filter(published.gt(now - 1.days())) .filter(published.gt(now - 1.days()))

View file

@ -10,7 +10,7 @@ use diesel::{dsl::*, result::Error, *};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Queryable, Identifiable, PartialEq, Debug)] #[derive(Clone, Queryable, Identifiable, PartialEq, Debug)]
#[table_name = "user_"] #[table_name = "user_"]
pub struct User_ { pub struct User_ {
pub id: i32, pub id: i32,

View file

@ -26,20 +26,39 @@ pub extern crate serde_json;
pub extern crate sha2; pub extern crate sha2;
pub extern crate strum; pub extern crate strum;
pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
where
F: FnOnce(&diesel::PgConnection) -> T + Send + 'static,
T: Send + 'static,
{
let pool = pool.clone();
let res = actix_web::web::block(move || {
let conn = pool.get()?;
let res = (f)(&conn);
Ok(res) as Result<_, LemmyError>
})
.await?;
Ok(res)
}
pub mod api; pub mod api;
pub mod apub; pub mod apub;
pub mod db; pub mod db;
pub mod rate_limit; pub mod rate_limit;
pub mod request;
pub mod routes; pub mod routes;
pub mod schema; pub mod schema;
pub mod settings; pub mod settings;
pub mod version; pub mod version;
pub mod websocket; pub mod websocket;
use crate::settings::Settings; use crate::{
use actix_web::dev::ConnectionInfo; request::{retry, RecvError},
settings::Settings,
};
use actix_web::{client::Client, dev::ConnectionInfo};
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, Utc}; use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, Utc};
use isahc::prelude::*;
use itertools::Itertools; use itertools::Itertools;
use lettre::{ use lettre::{
smtp::{ smtp::{
@ -58,12 +77,35 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng};
use regex::{Regex, RegexBuilder}; use regex::{Regex, RegexBuilder};
use serde::Deserialize; use serde::Deserialize;
pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
pub type ConnectionId = usize; pub type ConnectionId = usize;
pub type PostId = i32; pub type PostId = i32;
pub type CommunityId = i32; pub type CommunityId = i32;
pub type UserId = i32; pub type UserId = i32;
pub type IPAddr = String; pub type IPAddr = String;
#[derive(Debug)]
pub struct LemmyError {
inner: failure::Error,
}
impl std::fmt::Display for LemmyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.inner.fmt(f)
}
}
impl actix_web::error::ResponseError for LemmyError {}
impl<T> From<T> for LemmyError
where
T: Into<failure::Error>,
{
fn from(t: T) -> Self {
LemmyError { inner: t.into() }
}
}
pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime<Utc> { pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime<Utc> {
DateTime::<Utc>::from_utc(ndt, Utc) DateTime::<Utc>::from_utc(ndt, Utc)
} }
@ -85,8 +127,10 @@ pub fn is_email_regex(test: &str) -> bool {
EMAIL_REGEX.is_match(test) EMAIL_REGEX.is_match(test)
} }
pub fn is_image_content_type(test: &str) -> Result<(), failure::Error> { pub async fn is_image_content_type(client: &Client, test: &str) -> Result<(), LemmyError> {
if isahc::get(test)? let response = retry(|| client.get(test).send()).await?;
if response
.headers() .headers()
.get("Content-Type") .get("Content-Type")
.ok_or_else(|| format_err!("No Content-Type header"))? .ok_or_else(|| format_err!("No Content-Type header"))?
@ -95,7 +139,7 @@ pub fn is_image_content_type(test: &str) -> Result<(), failure::Error> {
{ {
Ok(()) Ok(())
} else { } else {
Err(format_err!("Not an image type.")) Err(format_err!("Not an image type.").into())
} }
} }
@ -178,10 +222,15 @@ pub struct IframelyResponse {
html: Option<String>, html: Option<String>,
} }
pub fn fetch_iframely(url: &str) -> Result<IframelyResponse, failure::Error> { pub async fn fetch_iframely(client: &Client, url: &str) -> Result<IframelyResponse, LemmyError> {
let fetch_url = format!("http://iframely/oembed?url={}", url); let fetch_url = format!("http://iframely/oembed?url={}", url);
let text = isahc::get(&fetch_url)?.text()?;
let res: IframelyResponse = serde_json::from_str(&text)?; let mut response = retry(|| client.get(&fetch_url).send()).await?;
let res: IframelyResponse = response
.json()
.await
.map_err(|e| RecvError(e.to_string()))?;
Ok(res) Ok(res)
} }
@ -197,23 +246,30 @@ pub struct PictrsFile {
delete_token: String, delete_token: String,
} }
pub fn fetch_pictrs(image_url: &str) -> Result<PictrsResponse, failure::Error> { pub async fn fetch_pictrs(client: &Client, image_url: &str) -> Result<PictrsResponse, LemmyError> {
is_image_content_type(image_url)?; is_image_content_type(client, image_url).await?;
let fetch_url = format!( let fetch_url = format!(
"http://pictrs:8080/image/download?url={}", "http://pictrs:8080/image/download?url={}",
utf8_percent_encode(image_url, NON_ALPHANUMERIC) // TODO this might not be needed utf8_percent_encode(image_url, NON_ALPHANUMERIC) // TODO this might not be needed
); );
let text = isahc::get(&fetch_url)?.text()?;
let res: PictrsResponse = serde_json::from_str(&text)?; let mut response = retry(|| client.get(&fetch_url).send()).await?;
if res.msg == "ok" {
Ok(res) let response: PictrsResponse = response
.json()
.await
.map_err(|e| RecvError(e.to_string()))?;
if response.msg == "ok" {
Ok(response)
} else { } else {
Err(format_err!("{}", &res.msg)) Err(format_err!("{}", &response.msg).into())
} }
} }
fn fetch_iframely_and_pictrs_data( async fn fetch_iframely_and_pictrs_data(
client: &Client,
url: Option<String>, url: Option<String>,
) -> ( ) -> (
Option<String>, Option<String>,
@ -225,7 +281,7 @@ fn fetch_iframely_and_pictrs_data(
Some(url) => { Some(url) => {
// Fetch iframely data // Fetch iframely data
let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) = let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) =
match fetch_iframely(url) { match fetch_iframely(client, url).await {
Ok(res) => (res.title, res.description, res.thumbnail_url, res.html), Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
Err(e) => { Err(e) => {
error!("iframely err: {}", e); error!("iframely err: {}", e);
@ -235,7 +291,7 @@ fn fetch_iframely_and_pictrs_data(
// Fetch pictrs thumbnail // Fetch pictrs thumbnail
let pictrs_thumbnail = match iframely_thumbnail_url { let pictrs_thumbnail = match iframely_thumbnail_url {
Some(iframely_thumbnail_url) => match fetch_pictrs(&iframely_thumbnail_url) { Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await {
Ok(res) => Some(res.files[0].file.to_owned()), Ok(res) => Some(res.files[0].file.to_owned()),
Err(e) => { Err(e) => {
error!("pictrs err: {}", e); error!("pictrs err: {}", e);
@ -243,7 +299,7 @@ fn fetch_iframely_and_pictrs_data(
} }
}, },
// Try to generate a small thumbnail if iframely is not supported // Try to generate a small thumbnail if iframely is not supported
None => match fetch_pictrs(&url) { None => match fetch_pictrs(client, &url).await {
Ok(res) => Some(res.files[0].file.to_owned()), Ok(res) => Some(res.files[0].file.to_owned()),
Err(e) => { Err(e) => {
error!("pictrs err: {}", e); error!("pictrs err: {}", e);
@ -269,7 +325,7 @@ pub fn markdown_to_html(text: &str) -> String {
pub fn get_ip(conn_info: &ConnectionInfo) -> String { pub fn get_ip(conn_info: &ConnectionInfo) -> String {
conn_info conn_info
.remote() .remote_addr()
.unwrap_or("127.0.0.1:12345") .unwrap_or("127.0.0.1:12345")
.split(':') .split(':')
.next() .next()
@ -327,21 +383,25 @@ mod tests {
#[test] #[test]
fn test_mentions_regex() { fn test_mentions_regex() {
let text = "Just read a great blog post by [@tedu@honk.teduangst.com](/u/test). And another by !test_community@fish.teduangst.com . Another [@lemmy@lemmy_alpha:8540](/u/fish)"; let text = "Just read a great blog post by [@tedu@honk.teduangst.com](/u/test). And another by !test_community@fish.teduangst.com . Another [@lemmy@lemmy-alpha:8540](/u/fish)";
let mentions = scrape_text_for_mentions(text); let mentions = scrape_text_for_mentions(text);
assert_eq!(mentions[0].name, "tedu".to_string()); assert_eq!(mentions[0].name, "tedu".to_string());
assert_eq!(mentions[0].domain, "honk.teduangst.com".to_string()); assert_eq!(mentions[0].domain, "honk.teduangst.com".to_string());
assert_eq!(mentions[1].domain, "lemmy_alpha:8540".to_string()); assert_eq!(mentions[1].domain, "lemmy-alpha:8540".to_string());
} }
#[test] #[test]
fn test_image() { fn test_image() {
assert!(is_image_content_type("https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").is_ok()); actix_rt::System::new("tset_image").block_on(async move {
assert!(is_image_content_type( let client = actix_web::client::Client::default();
"https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20" assert!(is_image_content_type(&client, "https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").await.is_ok());
) assert!(is_image_content_type(&client,
.is_err()); "https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20"
)
.await.is_err()
);
});
} }
#[test] #[test]
@ -399,7 +459,7 @@ mod tests {
// These helped with testing // These helped with testing
// #[test] // #[test]
// fn test_iframely() { // fn test_iframely() {
// let res = fetch_iframely("https://www.redspark.nu/?p=15341"); // let res = fetch_iframely(client, "https://www.redspark.nu/?p=15341").await;
// assert!(res.is_ok()); // assert!(res.is_ok());
// } // }

View file

@ -4,10 +4,13 @@ extern crate diesel_migrations;
#[macro_use] #[macro_use]
pub extern crate lazy_static; pub extern crate lazy_static;
pub type DbPool = Pool<ConnectionManager<PgConnection>>;
use crate::lemmy_server::actix_web::dev::Service; use crate::lemmy_server::actix_web::dev::Service;
use actix::prelude::*; use actix::prelude::*;
use actix_web::{ use actix_web::{
body::Body, body::Body,
client::Client,
dev::{ServiceRequest, ServiceResponse}, dev::{ServiceRequest, ServiceResponse},
http::{ http::{
header::{CACHE_CONTROL, CONTENT_TYPE}, header::{CACHE_CONTROL, CONTENT_TYPE},
@ -20,14 +23,16 @@ use diesel::{
PgConnection, PgConnection,
}; };
use lemmy_server::{ use lemmy_server::{
blocking,
db::code_migrations::run_advanced_migrations, db::code_migrations::run_advanced_migrations,
rate_limit::{rate_limiter::RateLimiter, RateLimit}, rate_limit::{rate_limiter::RateLimiter, RateLimit},
routes::{api, federation, feeds, index, nodeinfo, webfinger}, routes::{api, federation, feeds, index, nodeinfo, webfinger},
settings::Settings, settings::Settings,
websocket::server::*, websocket::server::*,
LemmyError,
}; };
use regex::Regex; use regex::Regex;
use std::{io, sync::Arc}; use std::sync::Arc;
use tokio::sync::Mutex; use tokio::sync::Mutex;
lazy_static! { lazy_static! {
@ -41,7 +46,7 @@ lazy_static! {
embed_migrations!(); embed_migrations!();
#[actix_rt::main] #[actix_rt::main]
async fn main() -> io::Result<()> { async fn main() -> Result<(), LemmyError> {
env_logger::init(); env_logger::init();
let settings = Settings::get(); let settings = Settings::get();
@ -53,9 +58,12 @@ async fn main() -> io::Result<()> {
.unwrap_or_else(|_| panic!("Error connecting to {}", settings.get_database_url())); .unwrap_or_else(|_| panic!("Error connecting to {}", settings.get_database_url()));
// Run the migrations from code // Run the migrations from code
let conn = pool.get().unwrap(); blocking(&pool, move |conn| {
embedded_migrations::run(&conn).unwrap(); embedded_migrations::run(conn)?;
run_advanced_migrations(&conn).unwrap(); run_advanced_migrations(conn)?;
Ok(()) as Result<(), LemmyError>
})
.await??;
// Set up the rate limiter // Set up the rate limiter
let rate_limiter = RateLimit { let rate_limiter = RateLimit {
@ -63,7 +71,7 @@ async fn main() -> io::Result<()> {
}; };
// Set up websocket server // Set up websocket server
let server = ChatServer::startup(pool.clone(), rate_limiter.clone()).start(); let server = ChatServer::startup(pool.clone(), rate_limiter.clone(), Client::default()).start();
println!( println!(
"Starting http server at {}:{}", "Starting http server at {}:{}",
@ -79,6 +87,7 @@ async fn main() -> io::Result<()> {
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.data(pool.clone()) .data(pool.clone())
.data(server.clone()) .data(server.clone())
.data(Client::default())
// The routes // The routes
.configure(move |cfg| api::config(cfg, &rate_limiter)) .configure(move |cfg| api::config(cfg, &rate_limiter))
.configure(federation::config) .configure(federation::config)
@ -98,7 +107,9 @@ async fn main() -> io::Result<()> {
}) })
.bind((settings.bind, settings.port))? .bind((settings.bind, settings.port))?
.run() .run()
.await .await?;
Ok(())
} }
fn add_cache_headers<S>( fn add_cache_headers<S>(

View file

@ -1,5 +1,5 @@
use super::{IPAddr, Settings}; use super::{IPAddr, Settings};
use crate::{api::APIError, get_ip, settings::RateLimitConfig}; use crate::{get_ip, settings::RateLimitConfig, LemmyError};
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use futures::future::{ok, Ready}; use futures::future::{ok, Ready};
use rate_limiter::{RateLimitType, RateLimiter}; use rate_limiter::{RateLimitType, RateLimiter};
@ -15,6 +15,8 @@ pub mod rate_limiter;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RateLimit { pub struct RateLimit {
// it might be reasonable to use a std::sync::Mutex here, since we don't need to lock this
// across await points
pub rate_limiter: Arc<Mutex<RateLimiter>>, pub rate_limiter: Arc<Mutex<RateLimiter>>,
} }
@ -57,17 +59,11 @@ impl RateLimited {
fut: impl Future<Output = Result<T, E>>, fut: impl Future<Output = Result<T, E>>,
) -> Result<T, E> ) -> Result<T, E>
where where
E: From<failure::Error>, E: From<LemmyError>,
{ {
let rate_limit: RateLimitConfig = actix_web::web::block(move || { // Does not need to be blocking because the RwLock in settings never held across await points,
// needs to be in a web::block because the RwLock in settings is from stdlib // and the operation here locks only long enough to clone
Ok(Settings::get().rate_limit) as Result<_, failure::Error> let rate_limit: RateLimitConfig = Settings::get().rate_limit;
})
.await
.map_err(|e| match e {
actix_web::error::BlockingError::Error(e) => e,
_ => APIError::err("Operation canceled").into(),
})?;
// before // before
{ {
@ -83,6 +79,7 @@ impl RateLimited {
false, false,
)?; )?;
drop(limiter);
return fut.await; return fut.await;
} }
RateLimitType::Post => { RateLimitType::Post => {

View file

@ -1,6 +1,5 @@
use super::IPAddr; use super::IPAddr;
use crate::api::APIError; use crate::{api::APIError, LemmyError};
use failure::Error;
use log::debug; use log::debug;
use std::{collections::HashMap, time::SystemTime}; use std::{collections::HashMap, time::SystemTime};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@ -61,7 +60,7 @@ impl RateLimiter {
rate: i32, rate: i32,
per: i32, per: i32,
check_only: bool, check_only: bool,
) -> Result<(), Error> { ) -> Result<(), LemmyError> {
self.insert_ip(ip); self.insert_ip(ip);
if let Some(bucket) = self.buckets.get_mut(&type_) { if let Some(bucket) = self.buckets.get_mut(&type_) {
if let Some(rate_limit) = bucket.get_mut(ip) { if let Some(rate_limit) = bucket.get_mut(ip) {

51
server/src/request.rs Normal file
View file

@ -0,0 +1,51 @@
use crate::LemmyError;
use std::future::Future;
#[derive(Clone, Debug, Fail)]
#[fail(display = "Error sending request, {}", _0)]
struct SendError(pub String);
#[derive(Clone, Debug, Fail)]
#[fail(display = "Error receiving response, {}", _0)]
pub struct RecvError(pub String);
pub async fn retry<F, Fut, T>(f: F) -> Result<T, LemmyError>
where
F: Fn() -> Fut,
Fut: Future<Output = Result<T, actix_web::client::SendRequestError>>,
{
retry_custom(|| async { Ok((f)().await) }).await
}
pub async fn retry_custom<F, Fut, T>(f: F) -> Result<T, LemmyError>
where
F: Fn() -> Fut,
Fut: Future<Output = Result<Result<T, actix_web::client::SendRequestError>, LemmyError>>,
{
let mut response = Err(format_err!("connect timeout").into());
for _ in 0u8..3 {
match (f)().await? {
Ok(t) => return Ok(t),
Err(e) => {
if is_connect_timeout(&e) {
response = Err(SendError(e.to_string()).into());
continue;
}
return Err(SendError(e.to_string()).into());
}
}
}
response
}
fn is_connect_timeout(e: &actix_web::client::SendRequestError) -> bool {
if let actix_web::client::SendRequestError::Connect(e) = e {
if let actix_web::client::ConnectError::Timeout = e {
return true;
}
}
false
}

View file

@ -4,7 +4,7 @@ use crate::{
routes::{ChatServerParam, DbPoolParam}, routes::{ChatServerParam, DbPoolParam},
websocket::WebsocketInfo, websocket::WebsocketInfo,
}; };
use actix_web::{error::ErrorBadRequest, *}; use actix_web::{client::Client, error::ErrorBadRequest, *};
use serde::Serialize; use serde::Serialize;
pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
@ -150,6 +150,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
async fn perform<Request>( async fn perform<Request>(
data: Request, data: Request,
client: &Client,
db: DbPoolParam, db: DbPoolParam,
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> ) -> Result<HttpResponse, Error>
@ -162,9 +163,10 @@ where
id: None, id: None,
}; };
let oper: Oper<Request> = Oper::new(data); let oper: Oper<Request> = Oper::new(data, client.clone());
let res = web::block(move || oper.perform(db.get_ref().to_owned(), Some(ws_info))) let res = oper
.perform(&db, Some(ws_info))
.await .await
.map(|json| HttpResponse::Ok().json(json)) .map(|json| HttpResponse::Ok().json(json))
.map_err(ErrorBadRequest)?; .map_err(ErrorBadRequest)?;
@ -173,6 +175,7 @@ where
async fn route_get<Data>( async fn route_get<Data>(
data: web::Query<Data>, data: web::Query<Data>,
client: web::Data<Client>,
db: DbPoolParam, db: DbPoolParam,
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> ) -> Result<HttpResponse, Error>
@ -180,11 +183,12 @@ where
Data: Serialize + Send + 'static, Data: Serialize + Send + 'static,
Oper<Data>: Perform, Oper<Data>: Perform,
{ {
perform::<Data>(data.0, db, chat_server).await perform::<Data>(data.0, &client, db, chat_server).await
} }
async fn route_post<Data>( async fn route_post<Data>(
data: web::Json<Data>, data: web::Json<Data>,
client: web::Data<Client>,
db: DbPoolParam, db: DbPoolParam,
chat_server: ChatServerParam, chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> ) -> Result<HttpResponse, Error>
@ -192,5 +196,5 @@ where
Data: Serialize + Send + 'static, Data: Serialize + Send + 'static,
Oper<Data>: Perform, Oper<Data>: Perform,
{ {
perform::<Data>(data.0, db, chat_server).await perform::<Data>(data.0, &client, db, chat_server).await
} }

View file

@ -1,4 +1,5 @@
use crate::{ use crate::{
blocking,
db::{ db::{
comment_view::{ReplyQueryBuilder, ReplyView}, comment_view::{ReplyQueryBuilder, ReplyView},
community::Community, community::Community,
@ -12,6 +13,7 @@ use crate::{
markdown_to_html, markdown_to_html,
routes::DbPoolParam, routes::DbPoolParam,
settings::Settings, settings::Settings,
LemmyError,
}; };
use actix_web::{error::ErrorBadRequest, *}; use actix_web::{error::ErrorBadRequest, *};
use chrono::{DateTime, NaiveDateTime, Utc}; use chrono::{DateTime, NaiveDateTime, Utc};
@ -43,21 +45,20 @@ pub fn config(cfg: &mut web::ServiceConfig) {
} }
async fn get_all_feed(info: web::Query<Params>, db: DbPoolParam) -> Result<HttpResponse, Error> { async fn get_all_feed(info: web::Query<Params>, db: DbPoolParam) -> Result<HttpResponse, Error> {
let res = web::block(move || { let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?;
let conn = db.get()?;
get_feed_all_data(&conn, &get_sort_type(info)?) let rss = blocking(&db, move |conn| get_feed_all_data(conn, &sort_type))
}) .await?
.await .map_err(ErrorBadRequest)?;
.map(|rss| {
Ok(
HttpResponse::Ok() HttpResponse::Ok()
.content_type("application/rss+xml") .content_type("application/rss+xml")
.body(rss) .body(rss),
}) )
.map_err(ErrorBadRequest)?;
Ok(res)
} }
fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result<String, failure::Error> { fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result<String, LemmyError> {
let site_view = SiteView::read(&conn)?; let site_view = SiteView::read(&conn)?;
let posts = PostQueryBuilder::create(&conn) let posts = PostQueryBuilder::create(&conn)
@ -85,37 +86,34 @@ async fn get_feed(
info: web::Query<Params>, info: web::Query<Params>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>, db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let res = web::block(move || { let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?;
let conn = db.get()?;
let sort_type = get_sort_type(info)?; let request_type = match path.0.as_ref() {
"u" => RequestType::User,
"c" => RequestType::Community,
"front" => RequestType::Front,
"inbox" => RequestType::Inbox,
_ => return Err(ErrorBadRequest(LemmyError::from(format_err!("wrong_type")))),
};
let request_type = match path.0.as_ref() { let param = path.1.to_owned();
"u" => RequestType::User,
"c" => RequestType::Community,
"front" => RequestType::Front,
"inbox" => RequestType::Inbox,
_ => return Err(format_err!("wrong_type")),
};
let param = path.1.to_owned(); let builder = blocking(&db, move |conn| match request_type {
RequestType::User => get_feed_user(conn, &sort_type, param),
match request_type { RequestType::Community => get_feed_community(conn, &sort_type, param),
RequestType::User => get_feed_user(&conn, &sort_type, param), RequestType::Front => get_feed_front(conn, &sort_type, param),
RequestType::Community => get_feed_community(&conn, &sort_type, param), RequestType::Inbox => get_feed_inbox(conn, param),
RequestType::Front => get_feed_front(&conn, &sort_type, param),
RequestType::Inbox => get_feed_inbox(&conn, param),
}
}) })
.await .await?
.map(|builder| builder.build().unwrap().to_string()) .map_err(ErrorBadRequest)?;
.map(|rss| {
let rss = builder.build().map_err(ErrorBadRequest)?.to_string();
Ok(
HttpResponse::Ok() HttpResponse::Ok()
.content_type("application/rss+xml") .content_type("application/rss+xml")
.body(rss) .body(rss),
}) )
.map_err(ErrorBadRequest)?;
Ok(res)
} }
fn get_sort_type(info: web::Query<Params>) -> Result<SortType, ParseError> { fn get_sort_type(info: web::Query<Params>) -> Result<SortType, ParseError> {
@ -130,7 +128,7 @@ fn get_feed_user(
conn: &PgConnection, conn: &PgConnection,
sort_type: &SortType, sort_type: &SortType,
user_name: String, user_name: String,
) -> Result<ChannelBuilder, failure::Error> { ) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(&conn)?; let site_view = SiteView::read(&conn)?;
let user = User_::find_by_username(&conn, &user_name)?; let user = User_::find_by_username(&conn, &user_name)?;
let user_url = user.get_profile_url(); let user_url = user.get_profile_url();
@ -156,7 +154,7 @@ fn get_feed_community(
conn: &PgConnection, conn: &PgConnection,
sort_type: &SortType, sort_type: &SortType,
community_name: String, community_name: String,
) -> Result<ChannelBuilder, failure::Error> { ) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(&conn)?; let site_view = SiteView::read(&conn)?;
let community = Community::read_from_name(&conn, &community_name)?; let community = Community::read_from_name(&conn, &community_name)?;
@ -185,7 +183,7 @@ fn get_feed_front(
conn: &PgConnection, conn: &PgConnection,
sort_type: &SortType, sort_type: &SortType,
jwt: String, jwt: String,
) -> Result<ChannelBuilder, failure::Error> { ) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(&conn)?; let site_view = SiteView::read(&conn)?;
let user_id = Claims::decode(&jwt)?.claims.id; let user_id = Claims::decode(&jwt)?.claims.id;
@ -210,7 +208,7 @@ fn get_feed_front(
Ok(channel_builder) Ok(channel_builder)
} }
fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, failure::Error> { fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(&conn)?; let site_view = SiteView::read(&conn)?;
let user_id = Claims::decode(&jwt)?.claims.id; let user_id = Claims::decode(&jwt)?.claims.id;

View file

@ -1,8 +1,10 @@
use crate::{ use crate::{
apub::get_apub_protocol_string, apub::get_apub_protocol_string,
blocking,
db::site_view::SiteView, db::site_view::SiteView,
routes::DbPoolParam, routes::DbPoolParam,
version, version,
LemmyError,
Settings, Settings,
}; };
use actix_web::{body::Body, error::ErrorBadRequest, *}; use actix_web::{body::Body, error::ErrorBadRequest, *};
@ -15,7 +17,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
.route("/.well-known/nodeinfo", web::get().to(node_info_well_known)); .route("/.well-known/nodeinfo", web::get().to(node_info_well_known));
} }
async fn node_info_well_known() -> Result<HttpResponse<Body>, failure::Error> { async fn node_info_well_known() -> Result<HttpResponse<Body>, LemmyError> {
let node_info = NodeInfoWellKnown { let node_info = NodeInfoWellKnown {
links: NodeInfoWellKnownLinks { links: NodeInfoWellKnownLinks {
rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.0")?, rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.0")?,
@ -30,38 +32,34 @@ async fn node_info_well_known() -> Result<HttpResponse<Body>, failure::Error> {
} }
async fn node_info(db: DbPoolParam) -> Result<HttpResponse, Error> { async fn node_info(db: DbPoolParam) -> Result<HttpResponse, Error> {
let res = web::block(move || { let site_view = blocking(&db, SiteView::read)
let conn = db.get()?; .await?
let site_view = match SiteView::read(&conn) { .map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))?;
Ok(site_view) => site_view,
Err(_) => return Err(format_err!("not_found")), let protocols = if Settings::get().federation.enabled {
}; vec!["activitypub".to_string()]
let protocols = if Settings::get().federation.enabled { } else {
vec!["activitypub".to_string()] vec![]
} else { };
vec![]
}; let json = NodeInfo {
Ok(NodeInfo { version: "2.0".to_string(),
version: "2.0".to_string(), software: NodeInfoSoftware {
software: NodeInfoSoftware { name: "lemmy".to_string(),
name: "lemmy".to_string(), version: version::VERSION.to_string(),
version: version::VERSION.to_string(), },
protocols,
usage: NodeInfoUsage {
users: NodeInfoUsers {
total: site_view.number_of_users,
}, },
protocols, local_posts: site_view.number_of_posts,
usage: NodeInfoUsage { local_comments: site_view.number_of_comments,
users: NodeInfoUsers { open_registrations: site_view.open_registration,
total: site_view.number_of_users, },
}, };
local_posts: site_view.number_of_posts,
local_comments: site_view.number_of_comments, Ok(HttpResponse::Ok().json(json))
open_registrations: site_view.open_registration,
},
})
})
.await
.map(|json| HttpResponse::Ok().json(json))
.map_err(ErrorBadRequest)?;
Ok(res)
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]

View file

@ -1,6 +1,8 @@
use crate::{ use crate::{
blocking,
db::{community::Community, user::User_}, db::{community::Community, user::User_},
routes::DbPoolParam, routes::DbPoolParam,
LemmyError,
Settings, Settings,
}; };
use actix_web::{error::ErrorBadRequest, web::Query, *}; use actix_web::{error::ErrorBadRequest, web::Query, *};
@ -61,64 +63,58 @@ async fn get_webfinger_response(
info: Query<Params>, info: Query<Params>,
db: DbPoolParam, db: DbPoolParam,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let res = web::block(move || { let community_regex_parsed = WEBFINGER_COMMUNITY_REGEX
let conn = db.get()?; .captures(&info.resource)
.map(|c| c.get(1))
.flatten();
let community_regex_parsed = WEBFINGER_COMMUNITY_REGEX let user_regex_parsed = WEBFINGER_USER_REGEX
.captures(&info.resource) .captures(&info.resource)
.map(|c| c.get(1)) .map(|c| c.get(1))
.flatten(); .flatten();
let user_regex_parsed = WEBFINGER_USER_REGEX let url = if let Some(community_name) = community_regex_parsed {
.captures(&info.resource) let community_name = community_name.as_str().to_owned();
.map(|c| c.get(1)) // Make sure the requested community exists.
.flatten(); blocking(&db, move |conn| {
Community::read_from_name(conn, &community_name)
})
.await?
.map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))?
.actor_id
} else if let Some(user_name) = user_regex_parsed {
let user_name = user_name.as_str().to_owned();
// Make sure the requested user exists.
blocking(&db, move |conn| User_::read_from_name(conn, &user_name))
.await?
.map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))?
.actor_id
} else {
return Err(ErrorBadRequest(LemmyError::from(format_err!("not_found"))));
};
let url = if let Some(community_name) = community_regex_parsed { let json = WebFingerResponse {
// Make sure the requested community exists. subject: info.resource.to_owned(),
let community = match Community::read_from_name(&conn, &community_name.as_str()) { aliases: vec![url.to_owned()],
Ok(o) => o, links: vec![
Err(_) => return Err(format_err!("not_found")), WebFingerLink {
}; rel: Some("http://webfinger.net/rel/profile-page".to_string()),
community.actor_id type_: Some("text/html".to_string()),
} else if let Some(user_name) = user_regex_parsed { href: Some(url.to_owned()),
// Make sure the requested user exists. template: None,
let user = match User_::read_from_name(&conn, &user_name.as_str()) { },
Ok(o) => o, WebFingerLink {
Err(_) => return Err(format_err!("not_found")), rel: Some("self".to_string()),
}; type_: Some("application/activity+json".to_string()),
user.actor_id href: Some(url),
} else { template: None,
return Err(format_err!("not_found")); }, // TODO: this also needs to return the subscribe link once that's implemented
}; //{
// "rel": "http://ostatus.org/schema/1.0/subscribe",
// "template": "https://my_instance.com/authorize_interaction?uri={uri}"
//}
],
};
let wf_res = WebFingerResponse { Ok(HttpResponse::Ok().json(json))
subject: info.resource.to_owned(),
aliases: vec![url.to_owned()],
links: vec![
WebFingerLink {
rel: Some("http://webfinger.net/rel/profile-page".to_string()),
type_: Some("text/html".to_string()),
href: Some(url.to_owned()),
template: None,
},
WebFingerLink {
rel: Some("self".to_string()),
type_: Some("application/activity+json".to_string()),
href: Some(url),
template: None,
}, // TODO: this also needs to return the subscribe link once that's implemented
//{
// "rel": "http://ostatus.org/schema/1.0/subscribe",
// "template": "https://my_instance.com/authorize_interaction?uri={uri}"
//}
],
};
Ok(wf_res)
})
.await
.map(|json| HttpResponse::Ok().json(json))
.map_err(ErrorBadRequest)?;
Ok(res)
} }

View file

@ -1,5 +1,5 @@
use crate::LemmyError;
use config::{Config, ConfigError, Environment, File}; use config::{Config, ConfigError, Environment, File};
use failure::Error;
use serde::Deserialize; use serde::Deserialize;
use std::{env, fs, net::IpAddr, sync::RwLock}; use std::{env, fs, net::IpAddr, sync::RwLock};
@ -118,11 +118,11 @@ impl Settings {
format!("{}/api/v1", self.hostname) format!("{}/api/v1", self.hostname)
} }
pub fn read_config_file() -> Result<String, Error> { pub fn read_config_file() -> Result<String, LemmyError> {
Ok(fs::read_to_string(CONFIG_FILE)?) Ok(fs::read_to_string(CONFIG_FILE)?)
} }
pub fn save_config_file(data: &str) -> Result<String, Error> { pub fn save_config_file(data: &str) -> Result<String, LemmyError> {
fs::write(CONFIG_FILE, data)?; fs::write(CONFIG_FILE, data)?;
// Reload the new settings // Reload the new settings

View file

@ -6,7 +6,6 @@ use diesel::{
r2d2::{ConnectionManager, Pool}, r2d2::{ConnectionManager, Pool},
PgConnection, PgConnection,
}; };
use failure::Error;
use log::{error, info}; use log::{error, info};
use rand::{rngs::ThreadRng, Rng}; use rand::{rngs::ThreadRng, Rng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View file

@ -9,10 +9,13 @@ use crate::{
websocket::UserOperation, websocket::UserOperation,
CommunityId, CommunityId,
ConnectionId, ConnectionId,
DbPool,
IPAddr, IPAddr,
LemmyError,
PostId, PostId,
UserId, UserId,
}; };
use actix_web::client::Client;
/// Chat server sends this messages to session /// Chat server sends this messages to session
#[derive(Message)] #[derive(Message)]
@ -154,12 +157,16 @@ pub struct ChatServer {
/// Rate limiting based on rate type and IP addr /// Rate limiting based on rate type and IP addr
rate_limiter: RateLimit, rate_limiter: RateLimit,
/// An HTTP Client
client: Client,
} }
impl ChatServer { impl ChatServer {
pub fn startup( pub fn startup(
pool: Pool<ConnectionManager<PgConnection>>, pool: Pool<ConnectionManager<PgConnection>>,
rate_limiter: RateLimit, rate_limiter: RateLimit,
client: Client,
) -> ChatServer { ) -> ChatServer {
ChatServer { ChatServer {
sessions: HashMap::new(), sessions: HashMap::new(),
@ -169,6 +176,7 @@ impl ChatServer {
rng: rand::thread_rng(), rng: rand::thread_rng(),
pool, pool,
rate_limiter, rate_limiter,
client,
} }
} }
@ -236,7 +244,7 @@ impl ChatServer {
response: &Response, response: &Response,
post_id: PostId, post_id: PostId,
my_id: Option<ConnectionId>, my_id: Option<ConnectionId>,
) -> Result<(), Error> ) -> Result<(), LemmyError>
where where
Response: Serialize, Response: Serialize,
{ {
@ -260,7 +268,7 @@ impl ChatServer {
response: &Response, response: &Response,
community_id: CommunityId, community_id: CommunityId,
my_id: Option<ConnectionId>, my_id: Option<ConnectionId>,
) -> Result<(), Error> ) -> Result<(), LemmyError>
where where
Response: Serialize, Response: Serialize,
{ {
@ -283,7 +291,7 @@ impl ChatServer {
op: &UserOperation, op: &UserOperation,
response: &Response, response: &Response,
my_id: Option<ConnectionId>, my_id: Option<ConnectionId>,
) -> Result<(), Error> ) -> Result<(), LemmyError>
where where
Response: Serialize, Response: Serialize,
{ {
@ -305,7 +313,7 @@ impl ChatServer {
response: &Response, response: &Response,
recipient_id: UserId, recipient_id: UserId,
my_id: Option<ConnectionId>, my_id: Option<ConnectionId>,
) -> Result<(), Error> ) -> Result<(), LemmyError>
where where
Response: Serialize, Response: Serialize,
{ {
@ -328,7 +336,7 @@ impl ChatServer {
user_operation: &UserOperation, user_operation: &UserOperation,
comment: &CommentResponse, comment: &CommentResponse,
my_id: Option<ConnectionId>, my_id: Option<ConnectionId>,
) -> Result<(), Error> { ) -> Result<(), LemmyError> {
let mut comment_reply_sent = comment.clone(); let mut comment_reply_sent = comment.clone();
comment_reply_sent.comment.my_vote = None; comment_reply_sent.comment.my_vote = None;
comment_reply_sent.comment.user_id = None; comment_reply_sent.comment.user_id = None;
@ -366,7 +374,7 @@ impl ChatServer {
user_operation: &UserOperation, user_operation: &UserOperation,
post: &PostResponse, post: &PostResponse,
my_id: Option<ConnectionId>, my_id: Option<ConnectionId>,
) -> Result<(), Error> { ) -> Result<(), LemmyError> {
let community_id = post.post.community_id; let community_id = post.post.community_id;
// Don't send my data with it // Don't send my data with it
@ -394,7 +402,7 @@ impl ChatServer {
&mut self, &mut self,
msg: StandardMessage, msg: StandardMessage,
ctx: &mut Context<Self>, ctx: &mut Context<Self>,
) -> impl Future<Output = Result<String, Error>> { ) -> impl Future<Output = Result<String, LemmyError>> {
let addr = ctx.address(); let addr = ctx.address();
let pool = self.pool.clone(); let pool = self.pool.clone();
let rate_limiter = self.rate_limiter.clone(); let rate_limiter = self.rate_limiter.clone();
@ -404,6 +412,7 @@ impl ChatServer {
None => "blank_ip".to_string(), None => "blank_ip".to_string(),
}; };
let client = self.client.clone();
async move { async move {
let msg = msg; let msg = msg;
let json: Value = serde_json::from_str(&msg.msg)?; let json: Value = serde_json::from_str(&msg.msg)?;
@ -414,482 +423,109 @@ impl ChatServer {
let user_operation: UserOperation = UserOperation::from_str(&op)?; let user_operation: UserOperation = UserOperation::from_str(&op)?;
let args = Args {
client,
pool,
rate_limiter,
chatserver: addr,
id: msg.id,
ip,
op: user_operation.clone(),
data,
};
match user_operation { match user_operation {
// User ops // User ops
UserOperation::Login => { UserOperation::Login => do_user_operation::<Login>(args).await,
do_user_operation::<Login>(pool, rate_limiter, addr, msg.id, ip, user_operation, data) UserOperation::Register => do_user_operation::<Register>(args).await,
.await UserOperation::GetUserDetails => do_user_operation::<GetUserDetails>(args).await,
} UserOperation::GetReplies => do_user_operation::<GetReplies>(args).await,
UserOperation::Register => { UserOperation::AddAdmin => do_user_operation::<AddAdmin>(args).await,
do_user_operation::<Register>(pool, rate_limiter, addr, msg.id, ip, user_operation, data) UserOperation::BanUser => do_user_operation::<BanUser>(args).await,
.await UserOperation::GetUserMentions => do_user_operation::<GetUserMentions>(args).await,
} UserOperation::EditUserMention => do_user_operation::<EditUserMention>(args).await,
UserOperation::GetUserDetails => { UserOperation::MarkAllAsRead => do_user_operation::<MarkAllAsRead>(args).await,
do_user_operation::<GetUserDetails>( UserOperation::DeleteAccount => do_user_operation::<DeleteAccount>(args).await,
pool, UserOperation::PasswordReset => do_user_operation::<PasswordReset>(args).await,
rate_limiter, UserOperation::PasswordChange => do_user_operation::<PasswordChange>(args).await,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::GetReplies => {
do_user_operation::<GetReplies>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::AddAdmin => {
do_user_operation::<AddAdmin>(pool, rate_limiter, addr, msg.id, ip, user_operation, data)
.await
}
UserOperation::BanUser => {
do_user_operation::<BanUser>(pool, rate_limiter, addr, msg.id, ip, user_operation, data)
.await
}
UserOperation::GetUserMentions => {
do_user_operation::<GetUserMentions>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::EditUserMention => {
do_user_operation::<EditUserMention>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::MarkAllAsRead => {
do_user_operation::<MarkAllAsRead>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::DeleteAccount => {
do_user_operation::<DeleteAccount>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::PasswordReset => {
do_user_operation::<PasswordReset>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::PasswordChange => {
do_user_operation::<PasswordChange>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::CreatePrivateMessage => { UserOperation::CreatePrivateMessage => {
do_user_operation::<CreatePrivateMessage>( do_user_operation::<CreatePrivateMessage>(args).await
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::EditPrivateMessage => {
do_user_operation::<EditPrivateMessage>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::GetPrivateMessages => {
do_user_operation::<GetPrivateMessages>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::UserJoin => {
do_user_operation::<UserJoin>(pool, rate_limiter, addr, msg.id, ip, user_operation, data)
.await
}
UserOperation::SaveUserSettings => {
do_user_operation::<SaveUserSettings>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
} }
UserOperation::EditPrivateMessage => do_user_operation::<EditPrivateMessage>(args).await,
UserOperation::GetPrivateMessages => do_user_operation::<GetPrivateMessages>(args).await,
UserOperation::UserJoin => do_user_operation::<UserJoin>(args).await,
UserOperation::SaveUserSettings => do_user_operation::<SaveUserSettings>(args).await,
// Site ops // Site ops
UserOperation::GetModlog => { UserOperation::GetModlog => do_user_operation::<GetModlog>(args).await,
do_user_operation::<GetModlog>(pool, rate_limiter, addr, msg.id, ip, user_operation, data) UserOperation::CreateSite => do_user_operation::<CreateSite>(args).await,
.await UserOperation::EditSite => do_user_operation::<EditSite>(args).await,
} UserOperation::GetSite => do_user_operation::<GetSite>(args).await,
UserOperation::CreateSite => { UserOperation::GetSiteConfig => do_user_operation::<GetSiteConfig>(args).await,
do_user_operation::<CreateSite>( UserOperation::SaveSiteConfig => do_user_operation::<SaveSiteConfig>(args).await,
pool, UserOperation::Search => do_user_operation::<Search>(args).await,
rate_limiter, UserOperation::TransferCommunity => do_user_operation::<TransferCommunity>(args).await,
addr, UserOperation::TransferSite => do_user_operation::<TransferSite>(args).await,
msg.id, UserOperation::ListCategories => do_user_operation::<ListCategories>(args).await,
ip,
user_operation,
data,
)
.await
}
UserOperation::EditSite => {
do_user_operation::<EditSite>(pool, rate_limiter, addr, msg.id, ip, user_operation, data)
.await
}
UserOperation::GetSite => {
do_user_operation::<GetSite>(pool, rate_limiter, addr, msg.id, ip, user_operation, data)
.await
}
UserOperation::GetSiteConfig => {
do_user_operation::<GetSiteConfig>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::SaveSiteConfig => {
do_user_operation::<SaveSiteConfig>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::Search => {
do_user_operation::<Search>(pool, rate_limiter, addr, msg.id, ip, user_operation, data)
.await
}
UserOperation::TransferCommunity => {
do_user_operation::<TransferCommunity>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::TransferSite => {
do_user_operation::<TransferSite>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::ListCategories => {
do_user_operation::<ListCategories>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
// Community ops // Community ops
UserOperation::GetCommunity => { UserOperation::GetCommunity => do_user_operation::<GetCommunity>(args).await,
do_user_operation::<GetCommunity>( UserOperation::ListCommunities => do_user_operation::<ListCommunities>(args).await,
pool, UserOperation::CreateCommunity => do_user_operation::<CreateCommunity>(args).await,
rate_limiter, UserOperation::EditCommunity => do_user_operation::<EditCommunity>(args).await,
addr, UserOperation::FollowCommunity => do_user_operation::<FollowCommunity>(args).await,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::ListCommunities => {
do_user_operation::<ListCommunities>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::CreateCommunity => {
do_user_operation::<CreateCommunity>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::EditCommunity => {
do_user_operation::<EditCommunity>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::FollowCommunity => {
do_user_operation::<FollowCommunity>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::GetFollowedCommunities => { UserOperation::GetFollowedCommunities => {
do_user_operation::<GetFollowedCommunities>( do_user_operation::<GetFollowedCommunities>(args).await
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::BanFromCommunity => {
do_user_operation::<BanFromCommunity>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::AddModToCommunity => {
do_user_operation::<AddModToCommunity>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
} }
UserOperation::BanFromCommunity => do_user_operation::<BanFromCommunity>(args).await,
UserOperation::AddModToCommunity => do_user_operation::<AddModToCommunity>(args).await,
// Post ops // Post ops
UserOperation::CreatePost => { UserOperation::CreatePost => do_user_operation::<CreatePost>(args).await,
do_user_operation::<CreatePost>( UserOperation::GetPost => do_user_operation::<GetPost>(args).await,
pool, UserOperation::GetPosts => do_user_operation::<GetPosts>(args).await,
rate_limiter, UserOperation::EditPost => do_user_operation::<EditPost>(args).await,
addr, UserOperation::CreatePostLike => do_user_operation::<CreatePostLike>(args).await,
msg.id, UserOperation::SavePost => do_user_operation::<SavePost>(args).await,
ip,
user_operation,
data,
)
.await
}
UserOperation::GetPost => {
do_user_operation::<GetPost>(pool, rate_limiter, addr, msg.id, ip, user_operation, data)
.await
}
UserOperation::GetPosts => {
do_user_operation::<GetPosts>(pool, rate_limiter, addr, msg.id, ip, user_operation, data)
.await
}
UserOperation::EditPost => {
do_user_operation::<EditPost>(pool, rate_limiter, addr, msg.id, ip, user_operation, data)
.await
}
UserOperation::CreatePostLike => {
do_user_operation::<CreatePostLike>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::SavePost => {
do_user_operation::<SavePost>(pool, rate_limiter, addr, msg.id, ip, user_operation, data)
.await
}
// Comment ops // Comment ops
UserOperation::CreateComment => { UserOperation::CreateComment => do_user_operation::<CreateComment>(args).await,
do_user_operation::<CreateComment>( UserOperation::EditComment => do_user_operation::<EditComment>(args).await,
pool, UserOperation::SaveComment => do_user_operation::<SaveComment>(args).await,
rate_limiter, UserOperation::GetComments => do_user_operation::<GetComments>(args).await,
addr, UserOperation::CreateCommentLike => do_user_operation::<CreateCommentLike>(args).await,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::EditComment => {
do_user_operation::<EditComment>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::SaveComment => {
do_user_operation::<SaveComment>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::GetComments => {
do_user_operation::<GetComments>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
UserOperation::CreateCommentLike => {
do_user_operation::<CreateCommentLike>(
pool,
rate_limiter,
addr,
msg.id,
ip,
user_operation,
data,
)
.await
}
} }
} }
} }
} }
async fn do_user_operation<'a, Data>( struct Args<'a> {
pool: Pool<ConnectionManager<PgConnection>>, client: Client,
pool: DbPool,
rate_limiter: RateLimit, rate_limiter: RateLimit,
chatserver: Addr<ChatServer>, chatserver: Addr<ChatServer>,
id: ConnectionId, id: ConnectionId,
ip: IPAddr, ip: IPAddr,
op: UserOperation, op: UserOperation,
data: &str, data: &'a str,
) -> Result<String, Error> }
async fn do_user_operation<'a, 'b, Data>(args: Args<'b>) -> Result<String, LemmyError>
where where
for<'de> Data: Deserialize<'de> + 'a, for<'de> Data: Deserialize<'de> + 'a,
Oper<Data>: Perform, Oper<Data>: Perform,
{ {
let Args {
client,
pool,
rate_limiter,
chatserver,
id,
ip,
op,
data,
} = args;
let ws_info = WebsocketInfo { let ws_info = WebsocketInfo {
chatserver, chatserver,
id: Some(id), id: Some(id),
@ -898,17 +534,14 @@ where
let data = data.to_string(); let data = data.to_string();
let op2 = op.clone(); let op2 = op.clone();
let client = client.clone();
let fut = async move { let fut = async move {
actix_web::web::block(move || { let pool = pool.clone();
let parsed_data: Data = serde_json::from_str(&data)?; let parsed_data: Data = serde_json::from_str(&data)?;
let res = Oper::new(parsed_data).perform(pool, Some(ws_info))?; let res = Oper::new(parsed_data, client)
to_json_string(&op, &res) .perform(&pool, Some(ws_info))
}) .await?;
.await to_json_string(&op, &res)
.map_err(|e| match e {
actix_web::error::BlockingError::Error(e) => e,
_ => APIError::err("Operation canceled").into(),
})
}; };
match op2 { match op2 {
@ -1109,7 +742,7 @@ struct WebsocketResponse<T> {
data: T, data: T,
} }
fn to_json_string<Response>(op: &UserOperation, data: &Response) -> Result<String, Error> fn to_json_string<Response>(op: &UserOperation, data: &Response) -> Result<String, LemmyError>
where where
Response: Serialize, Response: Serialize,
{ {

View file

@ -124,10 +124,10 @@ describe('main', () => {
}); });
describe('follow_accept', () => { describe('follow_accept', () => {
test('/u/lemmy_alpha follows and accepts lemmy_beta/c/main', async () => { test('/u/lemmy_alpha follows and accepts lemmy-beta/c/main', async () => {
// 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 searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy_beta:8550&type_=All&sort=TopAll`; let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`;
let searchResponse: SearchResponse = await fetch(searchUrl, { let searchResponse: SearchResponse = await fetch(searchUrl, {
method: 'GET', method: 'GET',
@ -215,7 +215,7 @@ describe('main', () => {
// Also make G follow B // Also make G follow B
// Use short-hand search url // Use short-hand search url
let searchUrlG = `${lemmyGammaApiUrl}/search?q=!main@lemmy_beta:8550&type_=All&sort=TopAll`; let searchUrlG = `${lemmyGammaApiUrl}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`;
let searchResponseG: SearchResponse = await fetch(searchUrlG, { let searchResponseG: SearchResponse = await fetch(searchUrlG, {
method: 'GET', method: 'GET',
@ -449,7 +449,7 @@ describe('main', () => {
// Lemmy alpha responds to their own comment, but mentions lemmy beta. // Lemmy alpha responds to their own comment, but mentions lemmy beta.
// Make sure lemmy beta gets that in their inbox. // Make sure lemmy beta gets that in their inbox.
let mentionContent = 'A test mention of @lemmy_beta@lemmy_beta:8550'; let mentionContent = 'A test mention of @lemmy_beta@lemmy-beta:8550';
let mentionCommentForm: CommentForm = { let mentionCommentForm: CommentForm = {
content: mentionContent, content: mentionContent,
post_id: 2, post_id: 2,
@ -550,7 +550,7 @@ describe('main', () => {
expect(createCommunityRes.community.name).toBe(communityName); expect(createCommunityRes.community.name).toBe(communityName);
// Cache it on lemmy_alpha // Cache it on lemmy_alpha
let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy_beta:8550/c/${communityName}&type_=All&sort=TopAll`; let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy-beta:8550/c/${communityName}&type_=All&sort=TopAll`;
let searchResponse: SearchResponse = await fetch(searchUrl, { let searchResponse: SearchResponse = await fetch(searchUrl, {
method: 'GET', method: 'GET',
}).then(d => d.json()); }).then(d => d.json());
@ -826,7 +826,7 @@ describe('main', () => {
expect(createCommunityRes.community.name).toBe(communityName); expect(createCommunityRes.community.name).toBe(communityName);
// Cache it on lemmy_alpha // Cache it on lemmy_alpha
let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy_beta:8550/c/${communityName}&type_=All&sort=TopAll`; let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy-beta:8550/c/${communityName}&type_=All&sort=TopAll`;
let searchResponse: SearchResponse = await fetch(searchUrl, { let searchResponse: SearchResponse = await fetch(searchUrl, {
method: 'GET', method: 'GET',
}).then(d => d.json()); }).then(d => d.json());
@ -1278,7 +1278,7 @@ describe('main', () => {
// Create a test comment on Gamma, make sure it gets announced to alpha // Create a test comment on Gamma, make sure it gets announced to alpha
let commentContent = let commentContent =
'A jest test federated comment announce, lets mention @lemmy_beta@lemmy_beta:8550'; 'A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8550';
let commentForm: CommentForm = { let commentForm: CommentForm = {
content: commentContent, content: commentContent,
@ -1417,7 +1417,7 @@ describe('main', () => {
expect(createChildCommentRes.comment.content).toBe(childCommentContent); expect(createChildCommentRes.comment.content).toBe(childCommentContent);
// Follow again, for other tests // Follow again, for other tests
let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy_beta:8550&type_=All&sort=TopAll`; let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`;
let searchResponse: SearchResponse = await fetch(searchUrl, { let searchResponse: SearchResponse = await fetch(searchUrl, {
method: 'GET', method: 'GET',