Merge branch 'federation'

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

View File

@ -20,6 +20,8 @@ services:
postgres:
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: jolt/iframely:v1.4.3

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',