Federation async (#848)

* Asyncify more

* I guess these changed

* Clean PR a bit

* Convert more away from failure error

* config changes for testing federation

* It was DNS

So actix-web's client relies on TRust DNS Resolver to figure out
where to send data, but TRust DNS Resolver seems to not play nice
with docker, which expressed itself as not resolving the name to
an IP address _the first time_ when making a request. The fix was
literally to make the request again (which I limited to 3 times
total, and not exceeding the request timeout in total)

* Only retry for connecterror

Since TRust DNS Resolver was causing ConnectError::Timeout,
this change limits the retry to only this error, returning
immediately for any other error

* Use http sig norm 0.4.0-alpha for actix-web 3.0 support

* Blocking function, retry http requests

* cargo +nightly fmt

* Only create one pictrs dir

* Don't yarn build

* cargo +nightly fmt
This commit is contained in:
Riley 2020-07-01 07:54:29 -05:00 committed by GitHub
parent 4c1cb5999c
commit a074564458
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 3846 additions and 2735 deletions

View file

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

View file

@ -5,17 +5,21 @@ 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
for Item in alpha beta gamma ; do
sudo mkdir -p volumes/pictrs_$Item
sudo chown -R 991:991 volumes/pictrs_$Item
done
sudo mkdir -p volumes/pictrs_alpha
sudo chown -R 991:991 volumes/pictrs_alpha
sudo docker-compose --file ../federation/docker-compose.yml --project-directory . up -d
pushd ../../ui
yarn
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

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
RUN mkdir /app/dist/documentation/ -p \
&& 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 server/config/defaults.hjson /app/config/defaults.hjson

View file

@ -12,28 +12,33 @@ services:
- ../federation/nginx.conf:/etc/nginx/nginx.conf
restart: on-failure
depends_on:
- lemmy_alpha
- pictrs_alpha
- lemmy_beta
- pictrs_beta
- lemmy_gamma
- pictrs_gamma
- lemmy-alpha
- pictrs
- lemmy-beta
- lemmy-gamma
- 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
environment:
- LEMMY_HOSTNAME=lemmy_alpha:8540
- LEMMY_HOSTNAME=lemmy-alpha:8540
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_alpha:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FRONT_END_DIR=/app/dist
- LEMMY_FEDERATION__ENABLED=true
- 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_SETUP__ADMIN_USERNAME=lemmy_alpha
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy_alpha
- LEMMY_SETUP__SITE_NAME=lemmy-alpha
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
@ -46,26 +51,21 @@ services:
- POSTGRES_DB=lemmy
volumes:
- ./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
environment:
- LEMMY_HOSTNAME=lemmy_beta:8550
- LEMMY_HOSTNAME=lemmy-beta:8550
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_beta:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FRONT_END_DIR=/app/dist
- LEMMY_FEDERATION__ENABLED=true
- 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_SETUP__ADMIN_USERNAME=lemmy_beta
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy_beta
- LEMMY_SETUP__SITE_NAME=lemmy-beta
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
@ -78,26 +78,21 @@ services:
- POSTGRES_DB=lemmy
volumes:
- ./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
environment:
- LEMMY_HOSTNAME=lemmy_gamma:8560
- LEMMY_HOSTNAME=lemmy-gamma:8560
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_gamma:5432/lemmy
- LEMMY_JWT_SECRET=changeme
- LEMMY_FRONT_END_DIR=/app/dist
- LEMMY_FEDERATION__ENABLED=true
- 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_SETUP__ADMIN_USERNAME=lemmy_gamma
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy_gamma
- LEMMY_SETUP__SITE_NAME=lemmy-gamma
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
@ -110,11 +105,6 @@ services:
- POSTGRES_DB=lemmy
volumes:
- ./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:
image: dogbin/iframely:latest

View file

@ -12,7 +12,7 @@ http {
client_max_body_size 50M;
location / {
proxy_pass http://lemmy_alpha:8540;
proxy_pass http://lemmy-alpha:8540;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -26,7 +26,7 @@ http {
# pict-rs images
location /pictrs {
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 Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -52,7 +52,7 @@ http {
client_max_body_size 50M;
location / {
proxy_pass http://lemmy_beta:8550;
proxy_pass http://lemmy-beta:8550;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -66,7 +66,7 @@ http {
# pict-rs images
location /pictrs {
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 Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -92,7 +92,7 @@ http {
client_max_body_size 50M;
location / {
proxy_pass http://lemmy_gamma:8560;
proxy_pass http://lemmy-gamma:8560;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -106,7 +106,7 @@ http {
# pict-rs images
location /pictrs {
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 Host $host;
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"]}
failure = "0.1.8"
serde = { version = "1.0.105", features = ["derive"] }
actix = "0.9.0"
actix-web = "2.0.0"
actix-files = "0.2.1"
actix-web-actors = "2.0.0"
actix = "0.10.0-alpha.2"
actix-web = { version = "3.0.0-alpha.3", features = ["rustls"] }
actix-files = "0.3.0-alpha.1"
actix-web-actors = "3.0.0-alpha.1"
actix-rt = "1.1.1"
awc = "2.0.0-alpha.2"
log = "0.4.0"
env_logger = "0.7.1"
rand = "0.7.3"
@ -34,19 +35,19 @@ regex = "1.3.5"
lazy_static = "1.3.0"
lettre = "0.9.3"
lettre_email = "0.9.4"
sha2 = "0.8.1"
rss = "1.9.0"
htmlescape = "0.3.1"
url = { version = "2.1.1", features = ["serde"] }
config = {version = "0.10.1", default-features = false, features = ["hjson"] }
percent-encoding = "2.1.0"
isahc = "0.9.2"
comrak = "0.7"
openssl = "0.10"
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"
tokio = "0.2.21"
futures = "0.3.5"
itertools = "0.9.0"
uuid = { version = "0.8", features = ["serde", "v4"] }
sha2 = "0.9"
async-trait = "0.1.36"

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -16,6 +16,7 @@ use crate::{
FromApub,
ToApub,
},
blocking,
convert_datetime,
db::{
comment::{Comment, CommentForm},
@ -26,6 +27,8 @@ use crate::{
},
routes::DbPoolParam,
scrape_text_for_mentions,
DbPool,
LemmyError,
MentionData,
};
use activitystreams::{
@ -35,9 +38,7 @@ use activitystreams::{
object::{kind::NoteType, properties::ObjectProperties, Note},
};
use activitystreams_new::object::Tombstone;
use actix_web::{body::Body, web::Path, HttpResponse, Result};
use diesel::PgConnection;
use failure::Error;
use actix_web::{body::Body, client::Client, web::Path, HttpResponse};
use itertools::Itertools;
use log::debug;
use serde::Deserialize;
@ -51,32 +52,41 @@ pub struct CommentQuery {
pub async fn get_apub_comment(
info: Path<CommentQuery>,
db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> {
) -> Result<HttpResponse<Body>, LemmyError> {
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 {
Ok(create_apub_response(&comment.to_apub(&db.get().unwrap())?))
Ok(create_apub_response(&comment.to_apub(&db).await?))
} else {
Ok(create_apub_tombstone_response(&comment.to_tombstone()?))
}
}
#[async_trait::async_trait(?Send)]
impl ToApub for Comment {
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 oprops: &mut ObjectProperties = comment.as_mut();
let creator = User_::read(&conn, self.creator_id)?;
let post = Post::read(&conn, self.post_id)?;
let community = Community::read(&conn, post.community_id)?;
let creator_id = self.creator_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
// [post_ap_id, Option(parent_comment_ap_id)]
let mut in_reply_to_vec = vec![post.ap_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);
}
@ -97,7 +107,7 @@ impl ToApub for Comment {
Ok(comment)
}
fn to_tombstone(&self) -> Result<Tombstone, Error> {
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
create_tombstone(
self.deleted,
&self.ap_id,
@ -107,27 +117,34 @@ impl ToApub for Comment {
}
}
#[async_trait::async_trait(?Send)]
impl FromApub for CommentForm {
type ApubType = Note;
/// 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 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 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.
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
// For deeply nested comments, FromApub automatically gets called recursively
let parent_id: Option<i32> = match in_reply_tos.next() {
Some(parent_comment_uri) => {
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)
}
@ -157,17 +174,27 @@ impl FromApub for CommentForm {
}
}
#[async_trait::async_trait(?Send)]
impl ApubObjectType for Comment {
/// Send out information about a newly created comment, to the followers of the community.
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(conn)?;
let post = Post::read(&conn, self.post_id)?;
let community = Community::read(conn, post.community_id)?;
async fn send_create(
&self,
creator: &User_,
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 maa: MentionsAndAddresses =
collect_non_local_mentions_and_addresses(&conn, &self.content, &community)?;
let mut create = Create::new();
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_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(())
}
/// Send out information about an edited post, to the followers of the community.
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(&conn)?;
let post = Post::read(&conn, self.post_id)?;
let community = Community::read(&conn, post.community_id)?;
async fn send_update(
&self,
creator: &User_,
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 maa: MentionsAndAddresses =
collect_non_local_mentions_and_addresses(&conn, &self.content, &community)?;
let mut update = Update::new();
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_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(())
}
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(&conn)?;
let post = Post::read(&conn, self.post_id)?;
let community = Community::read(&conn, post.community_id)?;
async fn send_delete(
&self,
creator: &User_,
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 mut delete = Delete::default();
@ -228,18 +274,29 @@ impl ApubObjectType for Comment {
send_activity_to_community(
&creator,
&conn,
&community,
vec![community.get_shared_inbox_url()],
delete,
)?;
client,
pool,
)
.await?;
Ok(())
}
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(&conn)?;
let post = Post::read(&conn, self.post_id)?;
let community = Community::read(&conn, post.community_id)?;
async fn send_undo_delete(
&self,
creator: &User_,
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
let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
@ -274,18 +331,30 @@ impl ApubObjectType for Comment {
send_activity_to_community(
&creator,
&conn,
&community,
vec![community.get_shared_inbox_url()],
undo,
)?;
client,
pool,
)
.await?;
Ok(())
}
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(&conn)?;
let post = Post::read(&conn, self.post_id)?;
let community = Community::read(&conn, post.community_id)?;
async fn send_remove(
&self,
mod_: &User_,
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 mut remove = Remove::default();
@ -302,18 +371,29 @@ impl ApubObjectType for Comment {
send_activity_to_community(
&mod_,
&conn,
&community,
vec![community.get_shared_inbox_url()],
remove,
)?;
client,
pool,
)
.await?;
Ok(())
}
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(&conn)?;
let post = Post::read(&conn, self.post_id)?;
let community = Community::read(&conn, post.community_id)?;
async fn send_undo_remove(
&self,
mod_: &User_,
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
let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
@ -347,20 +427,33 @@ impl ApubObjectType for Comment {
send_activity_to_community(
&mod_,
&conn,
&community,
vec![community.get_shared_inbox_url()],
undo,
)?;
client,
pool,
)
.await?;
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl ApubLikeableType for Comment {
fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(&conn)?;
let post = Post::read(&conn, self.post_id)?;
let community = Community::read(&conn, post.community_id)?;
async fn send_like(
&self,
creator: &User_,
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 mut like = Like::new();
@ -376,18 +469,30 @@ impl ApubLikeableType for Comment {
send_activity_to_community(
&creator,
&conn,
&community,
vec![community.get_shared_inbox_url()],
like,
)?;
client,
pool,
)
.await?;
Ok(())
}
fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(&conn)?;
let post = Post::read(&conn, self.post_id)?;
let community = Community::read(&conn, post.community_id)?;
async fn send_dislike(
&self,
creator: &User_,
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 mut dislike = Dislike::new();
@ -403,18 +508,30 @@ impl ApubLikeableType for Comment {
send_activity_to_community(
&creator,
&conn,
&community,
vec![community.get_shared_inbox_url()],
dislike,
)?;
client,
pool,
)
.await?;
Ok(())
}
fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(&conn)?;
let post = Post::read(&conn, self.post_id)?;
let community = Community::read(&conn, post.community_id)?;
async fn send_undo_like(
&self,
creator: &User_,
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 mut like = Like::new();
@ -446,11 +563,13 @@ impl ApubLikeableType for Comment {
send_activity_to_community(
&creator,
&conn,
&community,
vec![community.get_shared_inbox_url()],
undo,
)?;
client,
pool,
)
.await?;
Ok(())
}
}
@ -464,11 +583,12 @@ struct MentionsAndAddresses {
/// This takes a comment, and builds a list of to_addresses, inboxes,
/// and mention tags, so they know where to be sent to.
/// Addresses are the users / addresses that go in the cc field.
fn collect_non_local_mentions_and_addresses(
conn: &PgConnection,
async fn collect_non_local_mentions_and_addresses(
content: &str,
community: &Community,
) -> Result<MentionsAndAddresses, Error> {
client: &Client,
pool: &DbPool,
) -> Result<MentionsAndAddresses, LemmyError> {
let mut addressed_ccs = vec![community.get_followers_url()];
// Add the mention tag
@ -480,14 +600,17 @@ fn collect_non_local_mentions_and_addresses(
// Filter only the non-local ones
.filter(|m| !m.is_local())
.collect::<Vec<MentionData>>();
let mut mention_inboxes = Vec::new();
for mention in &mentions {
// 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);
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();
mention_inboxes.push(shared_inbox);
let mut mention_tag = Mention::new();
mention_tag

View file

@ -12,6 +12,7 @@ use crate::{
GroupExt,
ToApub,
},
blocking,
convert_datetime,
db::{
activity::insert_activity,
@ -21,6 +22,8 @@ use crate::{
},
naive_now,
routes::DbPoolParam,
DbPool,
LemmyError,
};
use activitystreams::{
activity::{Accept, Announce, Delete, Remove, Undo},
@ -35,22 +38,22 @@ use activitystreams::{
};
use activitystreams_ext::Ext3;
use activitystreams_new::{activity::Follow, object::Tombstone};
use actix_web::{body::Body, web::Path, HttpResponse, Result};
use diesel::PgConnection;
use failure::{Error, _core::fmt::Debug};
use actix_web::{body::Body, client::Client, web, HttpResponse};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
#[derive(Deserialize)]
pub struct CommunityQuery {
community_name: String,
}
#[async_trait::async_trait(?Send)]
impl ToApub for Community {
type Response = GroupExt;
// 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 oprops: &mut ObjectProperties = group.as_mut();
@ -58,10 +61,12 @@ impl ToApub for Community {
// then the rest of the moderators
// TODO Technically the instance admins can mod the community, but lets
// ignore that for now
let moderators = CommunityModeratorView::for_community(&conn, self.id)?
.into_iter()
.map(|m| m.user_actor_id)
.collect();
let id = self.id;
let moderators = blocking(pool, move |conn| {
CommunityModeratorView::for_community(&conn, id)
})
.await??;
let moderators = moderators.into_iter().map(|m| m.user_actor_id).collect();
oprops
.set_context_xsd_any_uri(context())?
@ -92,7 +97,12 @@ impl ToApub for Community {
.set_endpoints(endpoint_props)?
.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(
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(
self.deleted,
&self.actor_id,
@ -112,6 +122,7 @@ impl ToApub for Community {
}
}
#[async_trait::async_trait(?Send)]
impl ActorType for Community {
fn actor_id(&self) -> String {
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.
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 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())?)?;
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(())
}
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let group = self.to_apub(conn)?;
async fn send_delete(
&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 mut delete = Delete::default();
@ -162,17 +184,25 @@ impl ActorType for Community {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.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,
// the community was the actor.
// 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(())
}
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let group = self.to_apub(conn)?;
async fn send_undo_delete(
&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 mut delete = Delete::default();
@ -203,17 +233,25 @@ impl ActorType for Community {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.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,
// the community was the actor.
// 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(())
}
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
let group = self.to_apub(conn)?;
async fn send_remove(
&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 mut remove = Remove::default();
@ -228,17 +266,25 @@ impl ActorType for Community {
.set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
.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,
// the community was the actor.
// 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(())
}
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
let group = self.to_apub(conn)?;
async fn send_undo_remove(
&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 mut remove = Remove::default();
@ -268,51 +314,69 @@ impl ActorType for Community {
.set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
.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,
// the community was the actor.
// 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(())
}
/// For a given community, returns the inboxes of all followers.
fn get_follower_inboxes(&self, conn: &PgConnection) -> Result<Vec<String>, Error> {
Ok(
CommunityFollowerView::for_community(conn, self.id)?
.into_iter()
.map(|c| get_shared_inbox(&c.user_actor_id))
.filter(|s| !s.is_empty())
.unique()
.collect(),
)
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<String>, LemmyError> {
let id = self.id;
let inboxes = blocking(pool, move |conn| {
CommunityFollowerView::for_community(conn, id)
})
.await??;
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!()
}
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!()
}
}
#[async_trait::async_trait(?Send)]
impl FromApub for CommunityForm {
type ApubType = GroupExt;
/// 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 oprops = &group.inner.object_props;
let aprops = &group.ext_two;
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 creator = creator_and_moderator_uris
.next()
.map(|c| get_or_fetch_and_upsert_remote_user(&c.to_string(), &conn).unwrap())
.unwrap();
let creator_uri = creator_and_moderator_uris.next().unwrap();
let creator = get_or_fetch_and_upsert_remote_user(creator_uri.as_str(), client, pool).await?;
Ok(CommunityForm {
name: oprops.get_name_xsd_string().unwrap().to_string(),
@ -342,14 +406,18 @@ impl FromApub for CommunityForm {
/// Return the community json over HTTP.
pub async fn get_apub_community_http(
info: Path<CommunityQuery>,
info: web::Path<CommunityQuery>,
db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> {
let community = Community::read_from_name(&&db.get()?, &info.community_name)?;
) -> Result<HttpResponse<Body>, LemmyError> {
let community = blocking(&db, move |conn| {
Community::read_from_name(conn, &info.community_name)
})
.await??;
if !community.deleted {
Ok(create_apub_response(
&community.to_apub(&db.get().unwrap())?,
))
let apub = community.to_apub(&db).await?;
Ok(create_apub_response(&apub))
} else {
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).
pub async fn get_apub_community_followers(
info: Path<CommunityQuery>,
info: web::Path<CommunityQuery>,
db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> {
let community = Community::read_from_name(&&db.get()?, &info.community_name)?;
) -> Result<HttpResponse<Body>, LemmyError> {
let community = blocking(&db, move |conn| {
Community::read_from_name(&conn, &info.community_name)
})
.await??;
let conn = db.get()?;
//As we are an object, we validated that the community id was valid
let community_followers = CommunityFollowerView::for_community(&conn, community.id).unwrap();
let community_id = community.id;
let community_followers = blocking(&db, move |conn| {
CommunityFollowerView::for_community(&conn, community_id)
})
.await??;
let mut collection = UnorderedCollection::default();
let oprops: &mut ObjectProperties = collection.as_mut();
@ -379,12 +451,13 @@ pub async fn get_apub_community_followers(
}
impl Community {
pub fn do_announce<A>(
pub async fn do_announce<A>(
activity: A,
community: &Community,
sender: &dyn ActorType,
conn: &PgConnection,
) -> Result<HttpResponse, Error>
client: &Client,
pool: &DbPool,
) -> Result<HttpResponse, LemmyError>
where
A: Activity + Base + Serialize + Debug,
{
@ -399,15 +472,16 @@ impl Community {
.set_actor_xsd_any_uri(community.actor_id.to_owned())?
.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
// 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()
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())
}

View file

@ -4,6 +4,7 @@ use crate::{
fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
ActorType,
},
blocking,
db::{
activity::insert_activity,
community::{Community, CommunityFollower, CommunityFollowerForm},
@ -11,14 +12,14 @@ use crate::{
Followable,
},
routes::{ChatServerParam, DbPoolParam},
LemmyError,
};
use activitystreams::activity::Undo;
use activitystreams_new::activity::Follow;
use actix_web::{web, HttpRequest, HttpResponse, Result};
use diesel::PgConnection;
use failure::{Error, _core::fmt::Debug};
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
use log::debug;
use serde::Deserialize;
use std::fmt::Debug;
#[serde(untagged)]
#[derive(Deserialize, Debug)]
@ -28,7 +29,7 @@ pub enum CommunityAcceptedObjects {
}
impl CommunityAcceptedObjects {
fn follow(&self) -> Result<Follow, Error> {
fn follow(&self) -> Result<Follow, LemmyError> {
match self {
CommunityAcceptedObjects::Follow(f) => Ok(f.to_owned()),
CommunityAcceptedObjects::Undo(u) => Ok(
@ -49,16 +50,22 @@ pub async fn community_inbox(
input: web::Json<CommunityAcceptedObjects>,
path: web::Path<String>,
db: DbPoolParam,
client: web::Data<Client>,
_chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> {
) -> Result<HttpResponse, LemmyError> {
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 {
return Err(format_err!(
"Received activity is addressed to remote community {}",
&community.actor_id
));
return Err(
format_err!(
"Received activity is addressed to remote community {}",
&community.actor_id
)
.into(),
);
}
debug!(
"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 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, &conn)?;
let community = get_or_fetch_and_upsert_remote_community(&community_uri, &conn)?;
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?;
verify(&request, &user)?;
match input {
CommunityAcceptedObjects::Follow(f) => handle_follow(&f, &user, &community, &conn),
CommunityAcceptedObjects::Undo(u) => handle_undo_follow(&u, &user, &community, &conn),
CommunityAcceptedObjects::Follow(f) => handle_follow(f, user, community, &client, db).await,
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
/// Accept activity.
fn handle_follow(
follow: &Follow,
user: &User_,
community: &Community,
conn: &PgConnection,
) -> Result<HttpResponse, Error> {
insert_activity(&conn, user.id, &follow, false)?;
async fn handle_follow(
follow: Follow,
user: User_,
community: Community,
client: &Client,
db: DbPoolParam,
) -> Result<HttpResponse, LemmyError> {
insert_activity(user.id, follow.clone(), false, &db).await?;
let community_follower_form = CommunityFollowerForm {
community_id: community.id,
@ -97,27 +103,34 @@ fn handle_follow(
};
// 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())
}
fn handle_undo_follow(
undo: &Undo,
user: &User_,
community: &Community,
conn: &PgConnection,
) -> Result<HttpResponse, Error> {
insert_activity(&conn, user.id, &undo, false)?;
async fn handle_undo_follow(
undo: Undo,
user: User_,
community: Community,
db: DbPoolParam,
) -> Result<HttpResponse, LemmyError> {
insert_activity(user.id, undo, false, &db).await?;
let community_follower_form = CommunityFollowerForm {
community_id: community.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())
}

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 diesel::PgConnection;
use failure::Error;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
@ -24,7 +26,7 @@ impl GroupExtension {
conn: &PgConnection,
category_id: i32,
sensitive: bool,
) -> Result<GroupExtension, Error> {
) -> Result<GroupExtension, LemmyError> {
let category = Category::read(conn, category_id)?;
let group_category = GroupCategory {
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 actix_web::HttpRequest;
use failure::Error;
use http::request::Builder;
use http_signature_normalization::Config;
use actix_web::{client::ClientRequest, HttpRequest};
use http_signature_normalization_actix::{
digest::{DigestClient, SignExt},
Config,
};
use log::debug;
use openssl::{
hash::MessageDigest,
@ -12,7 +13,7 @@ use openssl::{
sign::{Signer, Verifier},
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use sha2::{Digest, Sha256};
lazy_static! {
static ref HTTP_SIG_CONFIG: Config = Config::new();
@ -24,7 +25,7 @@ pub struct Keypair {
}
/// 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 pkey = PKey::from_rsa(rsa)?;
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.
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 private_key = actor.private_key();
let headers = request
.headers_ref()
.unwrap()
.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 digest_client = request
.signature_with_digest(
HTTP_SIG_CONFIG.clone(),
signing_key_id,
Sha256::new(),
activity,
move |signing_string| {
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
.begin_sign(
request.method_ref().unwrap().as_str(),
request
.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(base64::encode(signer.sign_to_vec()?)) as Result<_, LemmyError>
},
)
.await?;
Ok(signature_header_value)
Ok(digest_client)
}
pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), Error> {
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>>()?;
pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> {
let verified = HTTP_SIG_CONFIG
.begin_verify(
request.method().as_str(),
request.uri().path_and_query().unwrap().as_str(),
headers,
request.method(),
request.uri().path_and_query(),
request.headers().clone(),
)?
.verify(|signature, signing_string| -> Result<bool, Error> {
.verify(|signature, signing_string| -> Result<bool, LemmyError> {
debug!(
"Verifying with key {}, message {}",
&actor.public_key(),
@ -101,10 +87,7 @@ pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), Error>
debug!("verified signature for {}", &request.uri());
Ok(())
} else {
Err(format_err!(
"Invalid signature on request: {}",
&request.uri()
))
Err(format_err!("Invalid signature on request: {}", &request.uri()).into())
}
}

View file

@ -1,15 +1,14 @@
use activitystreams::object::Note;
use actix_web::Result;
use actix_web::client::Client;
use diesel::{result::Error::NotFound, PgConnection};
use failure::{Error, _core::fmt::Debug};
use isahc::prelude::*;
use log::debug;
use serde::Deserialize;
use std::time::Duration;
use std::{fmt::Debug, time::Duration};
use url::Url;
use crate::{
api::site::SearchResponse,
blocking,
db::{
comment::{Comment, CommentForm},
comment_view::CommentView,
@ -23,7 +22,10 @@ use crate::{
SearchType,
},
naive_now,
request::{retry, RecvError},
routes::nodeinfo::{NodeInfo, NodeInfoWellKnown},
DbPool,
LemmyError,
};
use crate::{
@ -43,36 +45,50 @@ use chrono::NaiveDateTime;
static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60;
// 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!(
"{}://{}/.well-known/nodeinfo",
get_apub_protocol_string(),
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,
/// 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
Response: for<'de> Deserialize<'de>,
{
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 text = Request::get(url.as_str())
.header("Accept", APUB_JSON_CONTENT_TYPE)
.connect_timeout(timeout)
.timeout(timeout)
.body(())?
.send()?
.text()?;
let res: Response = serde_json::from_str(&text)?;
Ok(res)
let json = retry(|| {
client
.get(url.as_str())
.header("Accept", APUB_JSON_CONTENT_TYPE)
.timeout(timeout)
.send()
})
.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.
@ -92,7 +108,11 @@ pub enum SearchAcceptedObjects {
/// http://lemmy_alpha:8540/u/lemmy_alpha, or @lemmy_alpha@lemmy_alpha:8540
/// http://lemmy_alpha:8540/post/3
/// 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
let query_url = if query.contains('@') {
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>>();
(format!("/c/{}", split2[1]), split[1])
} else {
return Err(format_err!("Invalid search query: {}", query));
return Err(format_err!("Invalid search query: {}", query).into());
}
} 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);
@ -126,22 +146,41 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchRespo
communities: vec![],
users: vec![],
};
match fetch_remote_object::<SearchAcceptedObjects>(&query_url)? {
let response = match fetch_remote_object::<SearchAcceptedObjects>(client, &query_url).await? {
SearchAcceptedObjects::Person(p) => {
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) => {
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
// 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) => {
let p = upsert_post(&PostForm::from_apub(&p, conn)?, conn)?;
response.posts = vec![PostView::read(conn, p.id, None)?];
let post_form = PostForm::from_apub(&p, client, pool).await?;
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) => {
let post_url = c
@ -151,41 +190,59 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchRespo
.next()
.unwrap()
.to_string();
// TODO: also fetch parent comments if any
let post = fetch_remote_object(&Url::parse(&post_url)?)?;
upsert_post(&PostForm::from_apub(&post, conn)?, conn)?;
let c = upsert_comment(&CommentForm::from_apub(&c, conn)?, conn)?;
response.comments = vec![CommentView::read(conn, c.id, None)?];
let post = fetch_remote_object(client, &Url::parse(&post_url)?).await?;
let post_form = PostForm::from_apub(&post, client, pool).await?;
let comment_form = CommentForm::from_apub(&c, client, pool).await?;
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)
}
/// 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,
conn: &PgConnection,
) -> Result<User_, Error> {
match User_::read_from_actor_id(&conn, &apub_id) {
Ok(u) => {
// If its older than a day, re-fetch it
if !u.local && should_refetch_actor(u.last_refreshed_at) {
debug!("Fetching and updating from remote user: {}", apub_id);
let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?;
let mut uf = UserForm::from_apub(&person, &conn)?;
uf.last_refreshed_at = Some(naive_now());
Ok(User_::update(&conn, u.id, &uf)?)
} else {
Ok(u)
}
client: &Client,
pool: &DbPool,
) -> Result<User_, LemmyError> {
let apub_id_owned = apub_id.to_owned();
let user = blocking(pool, move |conn| {
User_::read_from_actor_id(conn, &apub_id_owned)
})
.await?;
match user {
// If its older than a day, re-fetch it
Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => {
debug!("Fetching and updating from remote user: {}", apub_id);
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 {}) => {
debug!("Fetching and creating remote user: {}", apub_id);
let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?;
let uf = UserForm::from_apub(&person, &conn)?;
Ok(User_::create(conn, &uf)?)
let person = fetch_remote_object::<PersonExt>(client, &Url::parse(apub_id)?).await?;
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.
pub fn get_or_fetch_and_upsert_remote_community(
pub async fn get_or_fetch_and_upsert_remote_community(
apub_id: &str,
conn: &PgConnection,
) -> Result<Community, Error> {
match Community::read_from_actor_id(&conn, &apub_id) {
Ok(c) => {
if !c.local && should_refetch_actor(c.last_refreshed_at) {
debug!("Fetching and updating from remote community: {}", apub_id);
let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
let mut cf = CommunityForm::from_apub(&group, conn)?;
cf.last_refreshed_at = Some(naive_now());
Ok(Community::update(&conn, c.id, &cf)?)
} else {
Ok(c)
}
client: &Client,
pool: &DbPool,
) -> Result<Community, LemmyError> {
let apub_id_owned = apub_id.to_owned();
let community = blocking(pool, move |conn| {
Community::read_from_actor_id(conn, &apub_id_owned)
})
.await?;
match community {
Ok(c) if !c.local && should_refetch_actor(c.last_refreshed_at) => {
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 {}) => {
debug!("Fetching and creating remote community: {}", apub_id);
let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
let cf = CommunityForm::from_apub(&group, conn)?;
let community = Community::create(conn, &cf)?;
let group = fetch_remote_object::<GroupExt>(client, &Url::parse(apub_id)?).await?;
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
let creator_and_moderator_uris = group
@ -232,74 +297,105 @@ pub fn get_or_fetch_and_upsert_remote_community(
.object_props
.get_many_attributed_to_xsd_any_uris()
.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 community_moderator_form = CommunityModeratorForm {
community_id: community.id,
user_id: mod_.id,
};
CommunityModerator::join(&conn, &community_moderator_form)?;
let mut creator_and_moderators = Vec::new();
for uri in creator_and_moderator_uris {
let c_or_m = get_or_fetch_and_upsert_remote_user(uri.as_str(), client, pool).await?;
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)
}
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);
match existing {
Err(NotFound {}) => Ok(Post::create(conn, &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,
conn: &PgConnection,
) -> Result<Post, Error> {
match Post::read_from_apub_id(conn, post_ap_id) {
client: &Client,
pool: &DbPool,
) -> 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),
Err(NotFound {}) => {
debug!("Fetching and creating remote post: {}", post_ap_id);
let post = fetch_remote_object::<PageExt>(&Url::parse(post_ap_id)?)?;
let post_form = PostForm::from_apub(&post, conn)?;
Ok(Post::create(conn, &post_form)?)
let post = fetch_remote_object::<PageExt>(client, &Url::parse(post_ap_id)?).await?;
let post_form = PostForm::from_apub(&post, client, pool).await?;
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);
match existing {
Err(NotFound {}) => Ok(Comment::create(conn, &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,
conn: &PgConnection,
) -> Result<Comment, Error> {
match Comment::read_from_apub_id(conn, comment_ap_id) {
client: &Client,
pool: &DbPool,
) -> 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),
Err(NotFound {}) => {
debug!(
"Fetching and creating remote comment and its parents: {}",
comment_ap_id
);
let comment = fetch_remote_object::<Note>(&Url::parse(comment_ap_id)?)?;
let comment_form = CommentForm::from_apub(&comment, conn)?;
Ok(Comment::create(conn, &comment_form)?)
let comment = fetch_remote_object::<Note>(client, &Url::parse(comment_ap_id)?).await?;
let comment_form = CommentForm::from_apub(&comment, client, pool).await?;
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
// and user actors
// 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 = fetch_remote_object::<OrderedCollection>(&outbox_url)?;
// let items = outbox.collection_props.get_many_items_base_boxes();
@ -317,11 +413,11 @@ pub fn get_or_fetch_and_insert_remote_comment(
// Ok(
// items
// .unwrap()
// .map(|obox: &BaseBox| -> Result<PostForm, Error> {
// .map(|obox: &BaseBox| -> Result<PostForm, LemmyError> {
// let page = obox.clone().to_concrete::<Page>()?;
// PostForm::from_page(&page, 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,
db::user::User_,
request::{retry, RecvError},
routes::webfinger::WebFingerResponse,
DbPool,
LemmyError,
MentionData,
Settings,
};
@ -28,11 +31,8 @@ use activitystreams::{
};
use activitystreams_ext::{Ext1, Ext2, Ext3};
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 diesel::PgConnection;
use failure::Error;
use isahc::prelude::*;
use log::debug;
use serde::Serialize;
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.
fn is_apub_id_valid(apub_id: &Url) -> bool {
debug!("Checking {}", apub_id);
if apub_id.scheme() != get_apub_protocol_string() {
debug!("invalid scheme: {:?}", apub_id.scheme());
return false;
}
@ -112,15 +114,27 @@ fn is_apub_id_valid(apub_id: &Url) -> bool {
.map(|d| d.to_string())
.collect();
match apub_id.domain() {
Some(d) => allowed_instances.contains(&d.to_owned()),
None => false,
Some(d) => {
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 {
type Response;
fn to_apub(&self, conn: &PgConnection) -> Result<Self::Response, Error>;
fn to_tombstone(&self) -> Result<Tombstone, Error>;
async fn to_apub(&self, pool: &DbPool) -> Result<Self::Response, LemmyError>;
fn to_tombstone(&self) -> Result<Tombstone, LemmyError>;
}
/// Updated is actually the deletion time
@ -129,7 +143,7 @@ fn create_tombstone(
object_id: &str,
updated: Option<NaiveDateTime>,
former_type: String,
) -> Result<Tombstone, Error> {
) -> Result<Tombstone, LemmyError> {
if deleted {
if let Some(updated) = updated {
let mut tombstone = Tombstone::new();
@ -138,37 +152,85 @@ fn create_tombstone(
tombstone.set_deleted(convert_datetime(updated).into());
Ok(tombstone)
} else {
Err(format_err!(
"Cant convert to tombstone because updated time was None."
))
Err(format_err!("Cant convert to tombstone because updated time was None.").into())
}
} else {
Err(format_err!(
"Cant convert object to tombstone if it wasnt deleted"
))
Err(format_err!("Cant convert object to tombstone if it wasnt deleted").into())
}
}
#[async_trait::async_trait(?Send)]
pub trait FromApub {
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
Self: Sized;
}
#[async_trait::async_trait(?Send)]
pub trait ApubObjectType {
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
async fn send_create(
&self,
creator: &User_,
client: &Client,
pool: &DbPool,
) -> 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 {
fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
async fn send_like(
&self,
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 {
@ -185,6 +247,7 @@ pub fn get_shared_inbox(actor_id: &str) -> String {
)
}
#[async_trait::async_trait(?Send)]
pub trait ActorType {
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,
// and a user can't be followed (yet)
#[allow(unused_variables)]
fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>;
fn send_unfollow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>;
async fn send_follow(
&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)]
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>;
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
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>;
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
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>;
/// 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
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!(
"{}://{}/.well-known/webfinger?resource=acct:{}@{}",
get_apub_protocol_string(),
@ -253,8 +354,14 @@ pub fn fetch_webfinger_url(mention: &MentionData) -> Result<String, Error> {
mention.domain
);
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
.links
.iter()
@ -263,5 +370,5 @@ pub fn fetch_webfinger_url(mention: &MentionData) -> Result<String, Error> {
link
.href
.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,
ToApub,
},
blocking,
convert_datetime,
db::{
community::Community,
@ -22,6 +23,8 @@ use crate::{
Crud,
},
routes::DbPoolParam,
DbPool,
LemmyError,
Settings,
};
use activitystreams::{
@ -32,9 +35,7 @@ use activitystreams::{
};
use activitystreams_ext::Ext1;
use activitystreams_new::object::Tombstone;
use actix_web::{body::Body, web::Path, HttpResponse, Result};
use diesel::PgConnection;
use failure::Error;
use actix_web::{body::Body, client::Client, web, HttpResponse};
use serde::Deserialize;
#[derive(Deserialize)]
@ -44,27 +45,33 @@ pub struct PostQuery {
/// Return the post json over HTTP.
pub async fn get_apub_post(
info: Path<PostQuery>,
info: web::Path<PostQuery>,
db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> {
) -> Result<HttpResponse<Body>, LemmyError> {
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 {
Ok(create_apub_response(&post.to_apub(&db.get().unwrap())?))
Ok(create_apub_response(&post.to_apub(&db).await?))
} else {
Ok(create_apub_tombstone_response(&post.to_tombstone()?))
}
}
#[async_trait::async_trait(?Send)]
impl ToApub for Post {
type Response = PageExt;
// 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 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
// 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))
}
fn to_tombstone(&self) -> Result<Tombstone, Error> {
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
create_tombstone(
self.deleted,
&self.ap_id,
@ -151,17 +158,26 @@ impl ToApub for Post {
}
}
#[async_trait::async_trait(?Send)]
impl FromApub for PostForm {
type ApubType = PageExt;
/// 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 oprops = &page.inner.object_props;
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 = 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() {
Some(any_image) => any_image
@ -221,11 +237,20 @@ impl FromApub for PostForm {
}
}
#[async_trait::async_trait(?Send)]
impl ApubObjectType for Post {
/// Send out information about a newly created post, to the followers of the community.
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = self.to_apub(conn)?;
let community = Community::read(conn, self.community_id)?;
async fn send_create(
&self,
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 mut create = Create::new();
@ -241,18 +266,28 @@ impl ApubObjectType for Post {
send_activity_to_community(
creator,
conn,
&community,
vec![community.get_shared_inbox_url()],
create,
)?;
client,
pool,
)
.await?;
Ok(())
}
/// Send out information about an edited post, to the followers of the community.
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = self.to_apub(conn)?;
let community = Community::read(conn, self.community_id)?;
async fn send_update(
&self,
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 mut update = Update::new();
@ -268,17 +303,27 @@ impl ApubObjectType for Post {
send_activity_to_community(
creator,
conn,
&community,
vec![community.get_shared_inbox_url()],
update,
)?;
client,
pool,
)
.await?;
Ok(())
}
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = self.to_apub(conn)?;
let community = Community::read(conn, self.community_id)?;
async fn send_delete(
&self,
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 mut delete = Delete::default();
@ -293,21 +338,29 @@ impl ApubObjectType for Post {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(BaseBox::from_concrete(page)?)?;
let community = Community::read(conn, self.community_id)?;
send_activity_to_community(
creator,
conn,
&community,
vec![community.get_shared_inbox_url()],
delete,
)?;
client,
pool,
)
.await?;
Ok(())
}
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = self.to_apub(conn)?;
let community = Community::read(conn, self.community_id)?;
async fn send_undo_delete(
&self,
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 mut delete = Delete::default();
@ -338,20 +391,29 @@ impl ApubObjectType for Post {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(delete)?;
let community = Community::read(conn, self.community_id)?;
send_activity_to_community(
creator,
conn,
&community,
vec![community.get_shared_inbox_url()],
undo,
)?;
client,
pool,
)
.await?;
Ok(())
}
fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = self.to_apub(conn)?;
let community = Community::read(conn, self.community_id)?;
async fn send_remove(
&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 mut remove = Remove::default();
@ -366,20 +428,29 @@ impl ApubObjectType for Post {
.set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
.set_object_base_box(BaseBox::from_concrete(page)?)?;
let community = Community::read(conn, self.community_id)?;
send_activity_to_community(
mod_,
conn,
&community,
vec![community.get_shared_inbox_url()],
remove,
)?;
client,
pool,
)
.await?;
Ok(())
}
fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = self.to_apub(conn)?;
let community = Community::read(conn, self.community_id)?;
async fn send_undo_remove(
&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 mut remove = Remove::default();
@ -409,22 +480,32 @@ impl ApubObjectType for Post {
.set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
.set_object_base_box(remove)?;
let community = Community::read(conn, self.community_id)?;
send_activity_to_community(
mod_,
conn,
&community,
vec![community.get_shared_inbox_url()],
undo,
)?;
client,
pool,
)
.await?;
Ok(())
}
}
#[async_trait::async_trait(?Send)]
impl ApubLikeableType for Post {
fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = self.to_apub(conn)?;
let community = Community::read(conn, self.community_id)?;
async fn send_like(
&self,
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 mut like = Like::new();
@ -440,17 +521,27 @@ impl ApubLikeableType for Post {
send_activity_to_community(
&creator,
&conn,
&community,
vec![community.get_shared_inbox_url()],
like,
)?;
client,
pool,
)
.await?;
Ok(())
}
fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = self.to_apub(conn)?;
let community = Community::read(conn, self.community_id)?;
async fn send_dislike(
&self,
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 mut dislike = Dislike::new();
@ -466,17 +557,27 @@ impl ApubLikeableType for Post {
send_activity_to_community(
&creator,
&conn,
&community,
vec![community.get_shared_inbox_url()],
dislike,
)?;
client,
pool,
)
.await?;
Ok(())
}
fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = self.to_apub(conn)?;
let community = Community::read(conn, self.community_id)?;
async fn send_undo_like(
&self,
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 mut like = Like::new();
@ -508,11 +609,13 @@ impl ApubLikeableType for Post {
send_activity_to_community(
&creator,
&conn,
&community,
vec![community.get_shared_inbox_url()],
undo,
)?;
client,
pool,
)
.await?;
Ok(())
}
}

View file

@ -7,6 +7,7 @@ use crate::{
FromApub,
ToApub,
},
blocking,
convert_datetime,
db::{
activity::insert_activity,
@ -14,6 +15,8 @@ use crate::{
user::User_,
Crud,
},
DbPool,
LemmyError,
};
use activitystreams::{
activity::{Create, Delete, Undo, Update},
@ -21,18 +24,21 @@ use activitystreams::{
object::{kind::NoteType, properties::ObjectProperties, Note},
};
use activitystreams_new::object::Tombstone;
use actix_web::Result;
use diesel::PgConnection;
use failure::Error;
use actix_web::client::Client;
#[async_trait::async_trait(?Send)]
impl ToApub for PrivateMessage {
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 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
.set_context_xsd_any_uri(context())?
@ -49,7 +55,7 @@ impl ToApub for PrivateMessage {
Ok(private_message)
}
fn to_tombstone(&self) -> Result<Tombstone, Error> {
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
create_tombstone(
self.deleted,
&self.ap_id,
@ -59,16 +65,24 @@ impl ToApub for PrivateMessage {
}
}
#[async_trait::async_trait(?Send)]
impl FromApub for PrivateMessageForm {
type ApubType = Note;
/// 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 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 = 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 {
creator_id: creator.id,
@ -91,12 +105,20 @@ impl FromApub for PrivateMessageForm {
}
}
#[async_trait::async_trait(?Send)]
impl ApubObjectType for PrivateMessage {
/// Send out information about a newly created private message
fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(conn)?;
async fn send_create(
&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 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();
create
@ -110,17 +132,24 @@ impl ApubObjectType for PrivateMessage {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.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(())
}
/// Send out information about an edited post, to the followers of the community.
fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(conn)?;
async fn send_update(
&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 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();
update
@ -134,16 +163,23 @@ impl ApubObjectType for PrivateMessage {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.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(())
}
fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(conn)?;
async fn send_delete(
&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 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();
delete
@ -157,16 +193,23 @@ impl ApubObjectType for PrivateMessage {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.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(())
}
fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(conn)?;
async fn send_undo_delete(
&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 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();
delete
@ -195,17 +238,27 @@ impl ApubObjectType for PrivateMessage {
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.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(())
}
fn send_remove(&self, _mod_: &User_, _conn: &PgConnection) -> Result<(), Error> {
async fn send_remove(
&self,
_mod_: &User_,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
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!()
}
}

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@ use crate::{
PersonExt,
ToApub,
},
blocking,
convert_datetime,
db::{
activity::insert_activity,
@ -15,6 +16,8 @@ use crate::{
},
naive_now,
routes::DbPoolParam,
DbPool,
LemmyError,
};
use activitystreams::{
actor::{properties::ApActorProperties, Person},
@ -29,9 +32,7 @@ use activitystreams_new::{
object::Tombstone,
prelude::*,
};
use actix_web::{body::Body, web::Path, HttpResponse, Result};
use diesel::PgConnection;
use failure::Error;
use actix_web::{body::Body, client::Client, web, HttpResponse};
use serde::Deserialize;
#[derive(Deserialize)]
@ -39,11 +40,12 @@ pub struct UserQuery {
user_name: String,
}
#[async_trait::async_trait(?Send)]
impl ToApub for User_ {
type Response = PersonExt;
// 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()
let mut person = Person::default();
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()))
}
fn to_tombstone(&self) -> Result<Tombstone, Error> {
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
unimplemented!()
}
}
#[async_trait::async_trait(?Send)]
impl ActorType for User_ {
fn actor_id(&self) -> String {
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.
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 mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
follow.set_context(context()).set_id(id.parse()?);
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(())
}
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 mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
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()?);
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(())
}
fn send_delete(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
async fn send_delete(
&self,
_creator: &User_,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
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!()
}
fn send_remove(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
async fn send_remove(
&self,
_creator: &User_,
_client: &Client,
_pool: &DbPool,
) -> Result<(), LemmyError> {
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!()
}
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!()
}
fn get_follower_inboxes(&self, _conn: &PgConnection) -> Result<Vec<String>, Error> {
async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<String>, LemmyError> {
unimplemented!()
}
}
#[async_trait::async_trait(?Send)]
impl FromApub for UserForm {
type ApubType = PersonExt;
/// 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 aprops = &person.ext_one;
let public_key: &PublicKey = &person.ext_two.public_key;
@ -210,10 +249,14 @@ impl FromApub for UserForm {
/// Return the user json over HTTP.
pub async fn get_apub_user_http(
info: Path<UserQuery>,
info: web::Path<UserQuery>,
db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> {
let user = User_::find_by_email_or_username(&&db.get()?, &info.user_name)?;
let u = user.to_apub(&db.get().unwrap())?;
) -> Result<HttpResponse<Body>, LemmyError> {
let user_name = info.into_inner().user_name;
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))
}

View file

@ -5,6 +5,7 @@ use crate::{
fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
FromApub,
},
blocking,
db::{
activity::insert_activity,
community::{CommunityFollower, CommunityFollowerForm},
@ -17,16 +18,17 @@ use crate::{
naive_now,
routes::{ChatServerParam, DbPoolParam},
websocket::{server::SendUserRoomMessage, UserOperation},
DbPool,
LemmyError,
};
use activitystreams::{
activity::{Accept, Create, Delete, Undo, Update},
object::Note,
};
use actix_web::{web, HttpRequest, HttpResponse, Result};
use diesel::PgConnection;
use failure::{Error, _core::fmt::Debug};
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
use log::debug;
use serde::Deserialize;
use std::fmt::Debug;
#[serde(untagged)]
#[derive(Deserialize, Debug)]
@ -43,51 +45,53 @@ pub async fn user_inbox(
request: HttpRequest,
input: web::Json<UserAcceptedObjects>,
path: web::Path<String>,
client: web::Data<Client>,
db: DbPoolParam,
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
let input = input.into_inner();
let conn = &db.get().unwrap();
let username = path.into_inner();
debug!("User {} received activity: {:?}", &username, &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) => {
receive_create_private_message(&c, &request, &conn, chat_server)
receive_create_private_message(*c, &request, &client, &db, chat_server).await
}
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) => {
receive_delete_private_message(&d, &request, &conn, chat_server)
receive_delete_private_message(*d, &request, &client, &db, chat_server).await
}
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.
fn receive_accept(
accept: &Accept,
async fn receive_accept(
accept: Accept,
request: &HttpRequest,
username: &str,
conn: &PgConnection,
) -> Result<HttpResponse, Error> {
client: &Client,
pool: &DbPool,
) -> Result<HttpResponse, LemmyError> {
let community_uri = accept
.accept_props
.get_actor_xsd_any_uri()
.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?;
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
let community_follower_form = CommunityFollowerForm {
@ -96,18 +100,22 @@ fn receive_accept(
};
// 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
Ok(HttpResponse::Ok().finish())
}
fn receive_create_private_message(
create: &Create,
async fn receive_create_private_message(
create: Create,
request: &HttpRequest,
conn: &PgConnection,
client: &Client,
pool: &DbPool,
chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> {
) -> Result<HttpResponse, LemmyError> {
let note = create
.create_props
.get_object_base_box()
@ -122,36 +130,44 @@ fn receive_create_private_message(
.unwrap()
.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)?;
insert_activity(&conn, user.id, &create, false)?;
insert_activity(user.id, create, false, pool).await?;
let private_message = PrivateMessageForm::from_apub(&note, &conn)?;
let inserted_private_message = PrivateMessage::create(&conn, &private_message)?;
let private_message = PrivateMessageForm::from_apub(&note, client, pool).await?;
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 {
message: message.to_owned(),
};
let message = blocking(pool, move |conn| {
PrivateMessageView::read(conn, inserted_private_message.id)
})
.await??;
let res = PrivateMessageResponse { message };
let recipient_id = res.message.recipient_id;
chat_server.do_send(SendUserRoomMessage {
op: UserOperation::CreatePrivateMessage,
response: res,
recipient_id: message.recipient_id,
recipient_id,
my_id: None,
});
Ok(HttpResponse::Ok().finish())
}
fn receive_update_private_message(
update: &Update,
async fn receive_update_private_message(
update: Update,
request: &HttpRequest,
conn: &PgConnection,
client: &Client,
pool: &DbPool,
chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> {
) -> Result<HttpResponse, LemmyError> {
let note = update
.update_props
.get_object_base_box()
@ -166,37 +182,52 @@ fn receive_update_private_message(
.unwrap()
.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)?;
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_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id;
PrivateMessage::update(conn, private_message_id, &private_message)?;
let private_message_form = PrivateMessageForm::from_apub(&note, client, pool).await?;
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 {
message: message.to_owned(),
};
let private_message_id = private_message.id;
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 {
op: UserOperation::EditPrivateMessage,
response: res,
recipient_id: message.recipient_id,
recipient_id,
my_id: None,
});
Ok(HttpResponse::Ok().finish())
}
fn receive_delete_private_message(
delete: &Delete,
async fn receive_delete_private_message(
delete: Delete,
request: &HttpRequest,
conn: &PgConnection,
client: &Client,
pool: &DbPool,
chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> {
) -> Result<HttpResponse, LemmyError> {
let note = delete
.delete_props
.get_object_base_box()
@ -211,15 +242,21 @@ fn receive_delete_private_message(
.unwrap()
.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)?;
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 {
content: private_message.content,
content: private_message_form.content,
recipient_id: private_message.recipient_id,
creator_id: private_message.creator_id,
deleted: Some(true),
@ -229,30 +266,40 @@ fn receive_delete_private_message(
published: None,
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 {
message: message.to_owned(),
};
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 {
op: UserOperation::EditPrivateMessage,
response: res,
recipient_id: message.recipient_id,
recipient_id,
my_id: None,
});
Ok(HttpResponse::Ok().finish())
}
fn receive_undo_delete_private_message(
undo: &Undo,
async fn receive_undo_delete_private_message(
undo: Undo,
request: &HttpRequest,
conn: &PgConnection,
client: &Client,
pool: &DbPool,
chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> {
) -> Result<HttpResponse, LemmyError> {
let delete = undo
.undo_props
.get_object_base_box()
@ -275,13 +322,19 @@ fn receive_undo_delete_private_message(
.unwrap()
.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)?;
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 {
content: private_message.content,
recipient_id: private_message.recipient_id,
@ -293,18 +346,25 @@ fn receive_undo_delete_private_message(
published: None,
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 {
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 {
op: UserOperation::EditPrivateMessage,
response: res,
recipient_id: message.recipient_id,
recipient_id,
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 failure::_core::fmt::Debug;
use log::debug;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt::Debug;
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[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,
user_id: i32,
data: &T,
local: bool,
) -> Result<(), failure::Error>
) -> Result<(), LemmyError>
where
T: Serialize + Debug,
{

View file

@ -10,21 +10,22 @@ use crate::{
apub::{extensions::signatures::generate_actor_keypair, make_apub_endpoint, EndpointType},
db::Crud,
naive_now,
LemmyError,
};
use diesel::*;
use failure::Error;
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)?;
community_updates_2020_04_02(&conn)?;
post_updates_2020_04_03(&conn)?;
comment_updates_2020_04_03(&conn)?;
private_message_updates_2020_05_05(&conn)?;
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::*;
info!("Running user_updates_2020_04_02");
@ -75,7 +76,7 @@ fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> {
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::*;
info!("Running community_updates_2020_04_02");
@ -119,7 +120,7 @@ fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> {
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::*;
info!("Running post_updates_2020_04_03");
@ -143,7 +144,7 @@ fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> {
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::*;
info!("Running comment_updates_2020_04_03");
@ -167,7 +168,7 @@ fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> {
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::*;
info!("Running private_message_updates_2020_05_05");

View file

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

View file

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

View file

@ -50,8 +50,8 @@ impl Crud<PasswordResetRequestForm> for PasswordResetRequest {
impl PasswordResetRequest {
pub fn create_token(conn: &PgConnection, from_user_id: i32, token: &str) -> Result<Self, Error> {
let mut hasher = Sha256::new();
hasher.input(token);
let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.result().to_vec());
hasher.update(token);
let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.finalize().to_vec());
let form = PasswordResetRequestForm {
user_id: from_user_id,
@ -62,8 +62,8 @@ impl PasswordResetRequest {
}
pub fn read_from_token(conn: &PgConnection, token: &str) -> Result<Self, Error> {
let mut hasher = Sha256::new();
hasher.input(token);
let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.result().to_vec());
hasher.update(token);
let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.finalize().to_vec());
password_reset_request
.filter(token_encrypted.eq(token_hash))
.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 serde::{Deserialize, Serialize};
#[derive(Queryable, Identifiable, PartialEq, Debug)]
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug)]
#[table_name = "user_"]
pub struct User_ {
pub id: i32,

View file

@ -26,20 +26,39 @@ pub extern crate serde_json;
pub extern crate sha2;
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 apub;
pub mod db;
pub mod rate_limit;
pub mod request;
pub mod routes;
pub mod schema;
pub mod settings;
pub mod version;
pub mod websocket;
use crate::settings::Settings;
use actix_web::dev::ConnectionInfo;
use crate::{
request::{retry, RecvError},
settings::Settings,
};
use actix_web::{client::Client, dev::ConnectionInfo};
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, Utc};
use isahc::prelude::*;
use itertools::Itertools;
use lettre::{
smtp::{
@ -58,12 +77,35 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng};
use regex::{Regex, RegexBuilder};
use serde::Deserialize;
pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
pub type ConnectionId = usize;
pub type PostId = i32;
pub type CommunityId = i32;
pub type UserId = i32;
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> {
DateTime::<Utc>::from_utc(ndt, Utc)
}
@ -85,8 +127,10 @@ pub fn is_email_regex(test: &str) -> bool {
EMAIL_REGEX.is_match(test)
}
pub fn is_image_content_type(test: &str) -> Result<(), failure::Error> {
if isahc::get(test)?
pub async fn is_image_content_type(client: &Client, test: &str) -> Result<(), LemmyError> {
let response = retry(|| client.get(test).send()).await?;
if response
.headers()
.get("Content-Type")
.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(())
} 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>,
}
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 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)
}
@ -197,23 +246,30 @@ pub struct PictrsFile {
delete_token: String,
}
pub fn fetch_pictrs(image_url: &str) -> Result<PictrsResponse, failure::Error> {
is_image_content_type(image_url)?;
pub async fn fetch_pictrs(client: &Client, image_url: &str) -> Result<PictrsResponse, LemmyError> {
is_image_content_type(client, image_url).await?;
let fetch_url = format!(
"http://pictrs:8080/image/download?url={}",
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)?;
if res.msg == "ok" {
Ok(res)
let mut response = retry(|| client.get(&fetch_url).send()).await?;
let response: PictrsResponse = response
.json()
.await
.map_err(|e| RecvError(e.to_string()))?;
if response.msg == "ok" {
Ok(response)
} 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>,
) -> (
Option<String>,
@ -225,7 +281,7 @@ fn fetch_iframely_and_pictrs_data(
Some(url) => {
// Fetch iframely data
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),
Err(e) => {
error!("iframely err: {}", e);
@ -235,7 +291,7 @@ fn fetch_iframely_and_pictrs_data(
// Fetch pictrs thumbnail
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()),
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
None => match fetch_pictrs(&url) {
None => match fetch_pictrs(client, &url).await {
Ok(res) => Some(res.files[0].file.to_owned()),
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 {
conn_info
.remote()
.remote_addr()
.unwrap_or("127.0.0.1:12345")
.split(':')
.next()
@ -327,21 +383,25 @@ mod tests {
#[test]
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);
assert_eq!(mentions[0].name, "tedu".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]
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());
assert!(is_image_content_type(
"https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20"
)
.is_err());
actix_rt::System::new("tset_image").block_on(async move {
let client = actix_web::client::Client::default();
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,
"https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20"
)
.await.is_err()
);
});
}
#[test]
@ -399,7 +459,7 @@ mod tests {
// These helped with testing
// #[test]
// 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());
// }

View file

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

View file

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

View file

@ -1,6 +1,5 @@
use super::IPAddr;
use crate::api::APIError;
use failure::Error;
use crate::{api::APIError, LemmyError};
use log::debug;
use std::{collections::HashMap, time::SystemTime};
use strum::IntoEnumIterator;
@ -61,7 +60,7 @@ impl RateLimiter {
rate: i32,
per: i32,
check_only: bool,
) -> Result<(), Error> {
) -> Result<(), LemmyError> {
self.insert_ip(ip);
if let Some(bucket) = self.buckets.get_mut(&type_) {
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},
websocket::WebsocketInfo,
};
use actix_web::{error::ErrorBadRequest, *};
use actix_web::{client::Client, error::ErrorBadRequest, *};
use serde::Serialize;
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>(
data: Request,
client: &Client,
db: DbPoolParam,
chat_server: ChatServerParam,
) -> Result<HttpResponse, Error>
@ -162,9 +163,10 @@ where
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
.map(|json| HttpResponse::Ok().json(json))
.map_err(ErrorBadRequest)?;
@ -173,6 +175,7 @@ where
async fn route_get<Data>(
data: web::Query<Data>,
client: web::Data<Client>,
db: DbPoolParam,
chat_server: ChatServerParam,
) -> Result<HttpResponse, Error>
@ -180,11 +183,12 @@ where
Data: Serialize + Send + 'static,
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>(
data: web::Json<Data>,
client: web::Data<Client>,
db: DbPoolParam,
chat_server: ChatServerParam,
) -> Result<HttpResponse, Error>
@ -192,5 +196,5 @@ where
Data: Serialize + Send + 'static,
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::{
blocking,
db::{
comment_view::{ReplyQueryBuilder, ReplyView},
community::Community,
@ -12,6 +13,7 @@ use crate::{
markdown_to_html,
routes::DbPoolParam,
settings::Settings,
LemmyError,
};
use actix_web::{error::ErrorBadRequest, *};
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> {
let res = web::block(move || {
let conn = db.get()?;
get_feed_all_data(&conn, &get_sort_type(info)?)
})
.await
.map(|rss| {
let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?;
let rss = blocking(&db, move |conn| get_feed_all_data(conn, &sort_type))
.await?
.map_err(ErrorBadRequest)?;
Ok(
HttpResponse::Ok()
.content_type("application/rss+xml")
.body(rss)
})
.map_err(ErrorBadRequest)?;
Ok(res)
.body(rss),
)
}
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 posts = PostQueryBuilder::create(&conn)
@ -85,37 +86,34 @@ async fn get_feed(
info: web::Query<Params>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> Result<HttpResponse, Error> {
let res = web::block(move || {
let conn = db.get()?;
let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?;
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() {
"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 param = path.1.to_owned();
match request_type {
RequestType::User => get_feed_user(&conn, &sort_type, param),
RequestType::Community => get_feed_community(&conn, &sort_type, param),
RequestType::Front => get_feed_front(&conn, &sort_type, param),
RequestType::Inbox => get_feed_inbox(&conn, param),
}
let builder = blocking(&db, move |conn| match request_type {
RequestType::User => get_feed_user(conn, &sort_type, param),
RequestType::Community => get_feed_community(conn, &sort_type, param),
RequestType::Front => get_feed_front(conn, &sort_type, param),
RequestType::Inbox => get_feed_inbox(conn, param),
})
.await
.map(|builder| builder.build().unwrap().to_string())
.map(|rss| {
.await?
.map_err(ErrorBadRequest)?;
let rss = builder.build().map_err(ErrorBadRequest)?.to_string();
Ok(
HttpResponse::Ok()
.content_type("application/rss+xml")
.body(rss)
})
.map_err(ErrorBadRequest)?;
Ok(res)
.body(rss),
)
}
fn get_sort_type(info: web::Query<Params>) -> Result<SortType, ParseError> {
@ -130,7 +128,7 @@ fn get_feed_user(
conn: &PgConnection,
sort_type: &SortType,
user_name: String,
) -> Result<ChannelBuilder, failure::Error> {
) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(&conn)?;
let user = User_::find_by_username(&conn, &user_name)?;
let user_url = user.get_profile_url();
@ -156,7 +154,7 @@ fn get_feed_community(
conn: &PgConnection,
sort_type: &SortType,
community_name: String,
) -> Result<ChannelBuilder, failure::Error> {
) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(&conn)?;
let community = Community::read_from_name(&conn, &community_name)?;
@ -185,7 +183,7 @@ fn get_feed_front(
conn: &PgConnection,
sort_type: &SortType,
jwt: String,
) -> Result<ChannelBuilder, failure::Error> {
) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(&conn)?;
let user_id = Claims::decode(&jwt)?.claims.id;
@ -210,7 +208,7 @@ fn get_feed_front(
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 user_id = Claims::decode(&jwt)?.claims.id;

View file

@ -1,8 +1,10 @@
use crate::{
apub::get_apub_protocol_string,
blocking,
db::site_view::SiteView,
routes::DbPoolParam,
version,
LemmyError,
Settings,
};
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));
}
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 {
links: NodeInfoWellKnownLinks {
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> {
let res = web::block(move || {
let conn = db.get()?;
let site_view = match SiteView::read(&conn) {
Ok(site_view) => site_view,
Err(_) => return Err(format_err!("not_found")),
};
let protocols = if Settings::get().federation.enabled {
vec!["activitypub".to_string()]
} else {
vec![]
};
Ok(NodeInfo {
version: "2.0".to_string(),
software: NodeInfoSoftware {
name: "lemmy".to_string(),
version: version::VERSION.to_string(),
let site_view = blocking(&db, SiteView::read)
.await?
.map_err(|_| ErrorBadRequest(LemmyError::from(format_err!("not_found"))))?;
let protocols = if Settings::get().federation.enabled {
vec!["activitypub".to_string()]
} else {
vec![]
};
let json = NodeInfo {
version: "2.0".to_string(),
software: NodeInfoSoftware {
name: "lemmy".to_string(),
version: version::VERSION.to_string(),
},
protocols,
usage: NodeInfoUsage {
users: NodeInfoUsers {
total: site_view.number_of_users,
},
protocols,
usage: NodeInfoUsage {
users: NodeInfoUsers {
total: site_view.number_of_users,
},
local_posts: site_view.number_of_posts,
local_comments: site_view.number_of_comments,
open_registrations: site_view.open_registration,
},
})
})
.await
.map(|json| HttpResponse::Ok().json(json))
.map_err(ErrorBadRequest)?;
Ok(res)
local_posts: site_view.number_of_posts,
local_comments: site_view.number_of_comments,
open_registrations: site_view.open_registration,
},
};
Ok(HttpResponse::Ok().json(json))
}
#[derive(Serialize, Deserialize, Debug)]

View file

@ -1,6 +1,8 @@
use crate::{
blocking,
db::{community::Community, user::User_},
routes::DbPoolParam,
LemmyError,
Settings,
};
use actix_web::{error::ErrorBadRequest, web::Query, *};
@ -61,64 +63,58 @@ async fn get_webfinger_response(
info: Query<Params>,
db: DbPoolParam,
) -> Result<HttpResponse, Error> {
let res = web::block(move || {
let conn = db.get()?;
let community_regex_parsed = WEBFINGER_COMMUNITY_REGEX
.captures(&info.resource)
.map(|c| c.get(1))
.flatten();
let community_regex_parsed = WEBFINGER_COMMUNITY_REGEX
.captures(&info.resource)
.map(|c| c.get(1))
.flatten();
let user_regex_parsed = WEBFINGER_USER_REGEX
.captures(&info.resource)
.map(|c| c.get(1))
.flatten();
let user_regex_parsed = WEBFINGER_USER_REGEX
.captures(&info.resource)
.map(|c| c.get(1))
.flatten();
let url = if let Some(community_name) = community_regex_parsed {
let community_name = community_name.as_str().to_owned();
// Make sure the requested community exists.
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 {
// Make sure the requested community exists.
let community = match Community::read_from_name(&conn, &community_name.as_str()) {
Ok(o) => o,
Err(_) => return Err(format_err!("not_found")),
};
community.actor_id
} else if let Some(user_name) = user_regex_parsed {
// Make sure the requested user exists.
let user = match User_::read_from_name(&conn, &user_name.as_str()) {
Ok(o) => o,
Err(_) => return Err(format_err!("not_found")),
};
user.actor_id
} else {
return Err(format_err!("not_found"));
};
let json = WebFingerResponse {
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}"
//}
],
};
let wf_res = WebFingerResponse {
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)
Ok(HttpResponse::Ok().json(json))
}

View file

@ -1,5 +1,5 @@
use crate::LemmyError;
use config::{Config, ConfigError, Environment, File};
use failure::Error;
use serde::Deserialize;
use std::{env, fs, net::IpAddr, sync::RwLock};
@ -118,11 +118,11 @@ impl Settings {
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)?)
}
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)?;
// Reload the new settings

View file

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

View file

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

View file

@ -124,10 +124,10 @@ describe('main', () => {
});
describe('follow_accept', () => {
test('/u/lemmy_alpha follows and accepts lemmy_beta/c/main', async () => {
// Make sure lemmy_beta/c/main is cached on lemmy_alpha
test('/u/lemmy_alpha follows and accepts lemmy-beta/c/main', async () => {
// Make sure lemmy-beta/c/main is cached on lemmy_alpha
// 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, {
method: 'GET',
@ -215,7 +215,7 @@ describe('main', () => {
// Also make G follow B
// 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, {
method: 'GET',
@ -449,7 +449,7 @@ describe('main', () => {
// Lemmy alpha responds to their own comment, but mentions lemmy beta.
// 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 = {
content: mentionContent,
post_id: 2,
@ -550,7 +550,7 @@ describe('main', () => {
expect(createCommunityRes.community.name).toBe(communityName);
// 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, {
method: 'GET',
}).then(d => d.json());
@ -826,7 +826,7 @@ describe('main', () => {
expect(createCommunityRes.community.name).toBe(communityName);
// 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, {
method: 'GET',
}).then(d => d.json());
@ -1278,7 +1278,7 @@ describe('main', () => {
// Create a test comment on Gamma, make sure it gets announced to alpha
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 = {
content: commentContent,
@ -1417,7 +1417,7 @@ describe('main', () => {
expect(createChildCommentRes.comment.content).toBe(childCommentContent);
// 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, {
method: 'GET',