#!/bin/bash
set -e
+sudo docker-compose --file ../federation/docker-compose.yml --project-directory . down
sudo rm -rf volumes
pushd ../../server/
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-alpha
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-beta
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-gamma
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-alpha
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-beta
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
- LEMMY_SETUP__SITE_NAME=lemmy-gamma
+ - LEMMY_RATE_LIMIT__POST=99999
+ - LEMMY_RATE_LIMIT__REGISTER=99999
- RUST_BACKTRACE=1
- RUST_LOG=debug
depends_on:
) -> Result<Self, Error> {
use crate::schema::comment::dsl::*;
diesel::update(comment.find(comment_id))
- .set(deleted.eq(new_deleted))
+ .set((
+ deleted.eq(new_deleted),
+ updated.eq(naive_now())
+ ))
.get_result::<Self>(conn)
}
) -> Result<Self, Error> {
use crate::schema::comment::dsl::*;
diesel::update(comment.find(comment_id))
- .set(removed.eq(new_removed))
+ .set((
+ removed.eq(new_removed),
+ updated.eq(naive_now())
+ ))
.get_result::<Self>(conn)
}
) -> Result<Self, Error> {
use crate::schema::community::dsl::*;
diesel::update(community.find(community_id))
- .set(deleted.eq(new_deleted))
+ .set((
+ deleted.eq(new_deleted),
+ updated.eq(naive_now())
+ ))
.get_result::<Self>(conn)
}
) -> Result<Self, Error> {
use crate::schema::community::dsl::*;
diesel::update(community.find(community_id))
- .set(removed.eq(new_removed))
+ .set((
+ removed.eq(new_removed),
+ updated.eq(naive_now())
+ ))
.get_result::<Self>(conn)
}
) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
diesel::update(post.find(post_id))
- .set(deleted.eq(new_deleted))
+ .set((
+ deleted.eq(new_deleted),
+ updated.eq(naive_now())
+ ))
.get_result::<Self>(conn)
}
) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
diesel::update(post.find(post_id))
- .set(removed.eq(new_removed))
+ .set((
+ removed.eq(new_removed),
+ updated.eq(naive_now())
+ ))
.get_result::<Self>(conn)
}
use crate::{
- api::{claims::Claims, is_mod_or_admin, APIError, Oper, Perform},
+ api::{
+ check_community_ban,
+ get_user_from_jwt,
+ get_user_from_jwt_opt,
+ is_mod_or_admin,
+ APIError,
+ Oper,
+ Perform,
+ },
apub::{ApubLikeableType, ApubObjectType},
blocking,
websocket::{
use lemmy_db::{
comment::*,
comment_view::*,
- community_view::*,
moderator::*,
post::*,
site_view::*,
websocket_info: Option<WebsocketInfo>,
) -> Result<CommentResponse, LemmyError> {
let data: &CreateComment = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let content_slurs_removed = remove_slurs(&data.content.to_owned());
content: content_slurs_removed,
parent_id: data.parent_id.to_owned(),
post_id: data.post_id,
- creator_id: user_id,
+ creator_id: user.id,
removed: None,
deleted: None,
read: None,
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());
- }
+ check_community_ban(user.id, post.community_id, pool).await?;
// Check if post is locked, no new comments
if post.locked {
let like_form = CommentLikeForm {
comment_id: inserted_comment.id,
post_id: data.post_id,
- user_id,
+ user_id: user.id,
score: 1,
};
updated_comment.send_like(&user, &self.client, pool).await?;
+ let user_id = user.id;
let comment_view = blocking(pool, move |conn| {
CommentView::read(&conn, inserted_comment.id, Some(user_id))
})
websocket_info: Option<WebsocketInfo>,
) -> Result<CommentResponse, LemmyError> {
let data: &EditComment = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let edit_id = data.edit_id;
let orig_comment =
blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??;
- // 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());
- }
-
- // Check for a community ban
- 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());
- }
+ check_community_ban(user.id, orig_comment.community_id, pool).await?;
// Verify that only the creator can edit
- if user_id != orig_comment.creator_id {
+ if user.id != orig_comment.creator_id {
return Err(APIError::err("no_comment_edit_allowed").into());
}
send_local_notifs(mentions, updated_comment, &user, post, pool, false).await?;
let edit_id = data.edit_id;
+ let user_id = user.id;
let comment_view = blocking(pool, move |conn| {
CommentView::read(conn, edit_id, Some(user_id))
})
websocket_info: Option<WebsocketInfo>,
) -> Result<CommentResponse, LemmyError> {
let data: &DeleteComment = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let edit_id = data.edit_id;
let orig_comment =
blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??;
- // 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());
- }
-
- // Check for a community ban
- 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());
- }
+ check_community_ban(user.id, orig_comment.community_id, pool).await?;
// Verify that only the creator can delete
- if user_id != orig_comment.creator_id {
+ if user.id != orig_comment.creator_id {
return Err(APIError::err("no_comment_edit_allowed").into());
}
// Refetch it
let edit_id = data.edit_id;
+ let user_id = user.id;
let comment_view = blocking(pool, move |conn| {
CommentView::read(conn, edit_id, Some(user_id))
})
websocket_info: Option<WebsocketInfo>,
) -> Result<CommentResponse, LemmyError> {
let data: &RemoveComment = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let edit_id = data.edit_id;
let orig_comment =
blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??;
- // 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());
- }
-
- // Check for a community ban
- 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());
- }
+ check_community_ban(user.id, orig_comment.community_id, pool).await?;
// Verify that only a mod or admin can remove
- is_mod_or_admin(pool, user_id, community_id).await?;
+ is_mod_or_admin(pool, user.id, orig_comment.community_id).await?;
// Do the remove
let removed = data.removed;
// Mod tables
let form = ModRemoveCommentForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
comment_id: data.edit_id,
removed: Some(removed),
reason: data.reason.to_owned(),
// Refetch it
let edit_id = data.edit_id;
+ let user_id = user.id;
let comment_view = blocking(pool, move |conn| {
CommentView::read(conn, edit_id, Some(user_id))
})
_websocket_info: Option<WebsocketInfo>,
) -> Result<CommentResponse, LemmyError> {
let data: &MarkCommentAsRead = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let edit_id = data.edit_id;
let orig_comment =
blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??;
- // 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());
- }
-
- // Check for a community ban
- 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());
- }
+ check_community_ban(user.id, orig_comment.community_id, pool).await?;
// Verify that only the recipient can mark as read
// Needs to fetch the parent comment / post to get the recipient
Some(pid) => {
let parent_comment =
blocking(pool, move |conn| CommentView::read(&conn, pid, None)).await??;
- if user_id != parent_comment.creator_id {
+ if user.id != parent_comment.creator_id {
return Err(APIError::err("no_comment_edit_allowed").into());
}
}
None => {
let parent_post_id = orig_comment.post_id;
let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
- if user_id != parent_post.creator_id {
+ if user.id != parent_post.creator_id {
return Err(APIError::err("no_comment_edit_allowed").into());
}
}
// Refetch it
let edit_id = data.edit_id;
+ let user_id = user.id;
let comment_view = blocking(pool, move |conn| {
CommentView::read(conn, edit_id, Some(user_id))
})
_websocket_info: Option<WebsocketInfo>,
) -> Result<CommentResponse, LemmyError> {
let data: &SaveComment = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let comment_saved_form = CommentSavedForm {
comment_id: data.comment_id,
- user_id,
+ user_id: user.id,
};
if data.save {
}
let comment_id = data.comment_id;
+ let user_id = user.id;
let comment_view = blocking(pool, move |conn| {
CommentView::read(conn, comment_id, Some(user_id))
})
websocket_info: Option<WebsocketInfo>,
) -> Result<CommentResponse, LemmyError> {
let data: &CreateCommentLike = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let mut recipient_ids = Vec::new();
let orig_comment =
blocking(pool, move |conn| CommentView::read(&conn, comment_id, None)).await??;
- // Check for a community ban
let post_id = orig_comment.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());
- }
+ check_community_ban(user.id, post.community_id, pool).await?;
let comment_id = data.comment_id;
let comment = blocking(pool, move |conn| Comment::read(conn, comment_id)).await??;
match comment.parent_id {
Some(parent_id) => {
let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??;
- if parent_comment.creator_id != user_id {
+ if parent_comment.creator_id != user.id {
let parent_user = blocking(pool, move |conn| {
User_::read(conn, parent_comment.creator_id)
})
let like_form = CommentLikeForm {
comment_id: data.comment_id,
post_id,
- user_id,
+ user_id: user.id,
score: data.score,
};
// Have to refetch the comment to get the current state
let comment_id = data.comment_id;
+ let user_id = user.id;
let liked_comment = blocking(pool, move |conn| {
CommentView::read(conn, comment_id, Some(user_id))
})
websocket_info: Option<WebsocketInfo>,
) -> Result<GetCommentsResponse, LemmyError> {
let data: &GetComments = &self.data;
-
- let user_claims: Option<Claims> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => Some(claims.claims),
- Err(_e) => None,
- },
- None => None,
- };
-
- let user_id = match &user_claims {
- Some(claims) => Some(claims.id),
- None => None,
- };
+ let user = get_user_from_jwt_opt(&data.auth, pool).await?;
+ let user_id = user.map(|u| u.id);
let type_ = ListingType::from_str(&data.type_)?;
let sort = SortType::from_str(&data.sort)?;
use super::*;
use crate::{
- api::{claims::Claims, is_admin, is_mod_or_admin, APIError, Oper, Perform},
+ api::{is_admin, is_mod_or_admin, APIError, Oper, Perform},
apub::ActorType,
blocking,
websocket::{
is_valid_community_name,
make_apub_endpoint,
naive_from_unix,
- slur_check,
- slurs_vec_to_str,
EndpointType,
};
use serde::{Deserialize, Serialize};
websocket_info: Option<WebsocketInfo>,
) -> Result<GetCommunityResponse, LemmyError> {
let data: &GetCommunity = &self.data;
-
- let user_id: Option<i32> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => {
- let user_id = claims.claims.id;
- Some(user_id)
- }
- Err(_e) => None,
- },
- None => None,
- };
+ let user = get_user_from_jwt_opt(&data.auth, pool).await?;
+ let user_id = user.map(|u| u.id);
let name = data.name.to_owned().unwrap_or_else(|| "main".to_string());
let community = match data.id {
_websocket_info: Option<WebsocketInfo>,
) -> Result<CommunityResponse, LemmyError> {
let data: &CreateCommunity = &self.data;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- if let Err(slurs) = slur_check(&data.name) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Err(slurs) = slur_check(&data.title) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Some(description) = &data.description {
- if let Err(slurs) = slur_check(description) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
+ check_slurs(&data.name)?;
+ check_slurs(&data.title)?;
+ check_slurs_opt(&data.description)?;
if !is_valid_community_name(&data.name) {
return Err(APIError::err("invalid_community_name").into());
}
- let user_id = claims.id;
-
- // Check for a site ban
- let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??;
- if user_view.banned {
- return Err(APIError::err("site_ban").into());
- }
-
// Double check for duplicate community actor_ids
let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string();
let actor_id_cloned = actor_id.to_owned();
title: data.title.to_owned(),
description: data.description.to_owned(),
category_id: data.category_id,
- creator_id: user_id,
+ creator_id: user.id,
removed: None,
deleted: None,
nsfw: data.nsfw,
let community_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id,
- user_id,
+ user_id: user.id,
};
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id,
- user_id,
+ user_id: user.id,
};
let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
return Err(APIError::err("community_follower_already_exists").into());
}
+ let user_id = user.id;
let community_view = blocking(pool, move |conn| {
CommunityView::read(conn, inserted_community.id, Some(user_id))
})
websocket_info: Option<WebsocketInfo>,
) -> Result<CommunityResponse, LemmyError> {
let data: &EditCommunity = &self.data;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
- if let Err(slurs) = slur_check(&data.title) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Some(description) = &data.description {
- if let Err(slurs) = slur_check(description) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
- // 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());
- }
+ check_slurs(&data.title)?;
+ check_slurs_opt(&data.description)?;
// Verify its a mod (only mods can edit it)
let edit_id = data.edit_id;
.map(|v| v.into_iter().map(|m| m.user_id).collect())
})
.await??;
- if !mods.contains(&user_id) {
+ if !mods.contains(&user.id) {
return Err(APIError::err("not_a_moderator").into());
}
// process for communities and users
let edit_id = data.edit_id;
+ let user_id = user.id;
let community_view = blocking(pool, move |conn| {
CommunityView::read(conn, edit_id, Some(user_id))
})
websocket_info: Option<WebsocketInfo>,
) -> Result<CommunityResponse, LemmyError> {
let data: &DeleteCommunity = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
- // 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 user = get_user_from_jwt(&data.auth, pool).await?;
// Verify its the creator (only a creator can delete the community)
let edit_id = data.edit_id;
let read_community = blocking(pool, move |conn| Community::read(conn, edit_id)).await??;
- if read_community.creator_id != user_id {
+ if read_community.creator_id != user.id {
return Err(APIError::err("no_community_edit_allowed").into());
}
}
let edit_id = data.edit_id;
+ let user_id = user.id;
let community_view = blocking(pool, move |conn| {
CommunityView::read(conn, edit_id, Some(user_id))
})
websocket_info: Option<WebsocketInfo>,
) -> Result<CommunityResponse, LemmyError> {
let data: &RemoveCommunity = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
- // 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 user = get_user_from_jwt(&data.auth, pool).await?;
// Verify its an admin (only an admin can remove a community)
- is_admin(pool, user_id).await?;
+ is_admin(pool, user.id).await?;
// Do the remove
let edit_id = data.edit_id;
None => None,
};
let form = ModRemoveCommunityForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
community_id: data.edit_id,
removed: Some(removed),
reason: data.reason.to_owned(),
}
let edit_id = data.edit_id;
+ let user_id = user.id;
let community_view = blocking(pool, move |conn| {
CommunityView::read(conn, edit_id, Some(user_id))
})
_websocket_info: Option<WebsocketInfo>,
) -> Result<ListCommunitiesResponse, LemmyError> {
let data: &ListCommunities = &self.data;
-
- // For logged in users, you need to get back subscribed, and settings
- let user: Option<User_> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => {
- let user_id = claims.claims.id;
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- Some(user)
- }
- Err(_e) => None,
- },
- None => None,
- };
+ let user = get_user_from_jwt_opt(&data.auth, pool).await?;
let user_id = match &user {
Some(user) => Some(user.id),
_websocket_info: Option<WebsocketInfo>,
) -> Result<CommunityResponse, LemmyError> {
let data: &FollowCommunity = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
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,
+ user_id: user.id,
};
if community.local {
return Err(APIError::err("community_follower_already_exists").into());
}
}
+ } else if data.follow {
+ // Dont actually add to the community followers here, because you need
+ // to wait for the accept
+ user
+ .send_follow(&community.actor_id, &self.client, pool)
+ .await?;
} else {
- 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, &self.client, pool)
- .await?;
- } else {
- 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());
- }
+ 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
}
+ // TODO: this needs to return a "pending" state, until Accept is received from the remote server
let community_id = data.community_id;
+ let user_id = user.id;
let community_view = blocking(pool, move |conn| {
CommunityView::read(conn, community_id, Some(user_id))
})
_websocket_info: Option<WebsocketInfo>,
) -> Result<GetFollowedCommunitiesResponse, LemmyError> {
let data: &GetFollowedCommunities = &self.data;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
+ let user_id = user.id;
let communities = match blocking(pool, move |conn| {
CommunityFollowerView::for_user(conn, user_id)
})
websocket_info: Option<WebsocketInfo>,
) -> Result<BanFromCommunityResponse, LemmyError> {
let data: &BanFromCommunity = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let community_id = data.community_id;
// Verify that only mods or admins can ban
- is_mod_or_admin(pool, user_id, community_id).await?;
+ is_mod_or_admin(pool, user.id, community_id).await?;
let community_user_ban_form = CommunityUserBanForm {
community_id: data.community_id,
};
let form = ModBanFromCommunityForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
community_id: data.community_id,
reason: data.reason.to_owned(),
websocket_info: Option<WebsocketInfo>,
) -> Result<AddModToCommunityResponse, LemmyError> {
let data: &AddModToCommunity = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id,
let community_id = data.community_id;
// Verify that only mods or admins can add mod
- is_mod_or_admin(pool, user_id, community_id).await?;
+ is_mod_or_admin(pool, user.id, community_id).await?;
if data.added {
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
// Mod tables
let form = ModAddCommunityForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
community_id: data.community_id,
removed: Some(!data.added),
_websocket_info: Option<WebsocketInfo>,
) -> Result<GetCommunityResponse, LemmyError> {
let data: &TransferCommunity = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let community_id = data.community_id;
let read_community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
admins.insert(0, creator_user);
// Make sure user is the creator, or an admin
- if user_id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user_id) {
+ if user.id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user.id) {
return Err(APIError::err("not_an_admin").into());
}
// Mod tables
let form = ModAddCommunityForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
community_id: data.community_id,
removed: Some(false),
blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??;
let community_id = data.community_id;
+ let user_id = user.id;
let community_view = match blocking(pool, move |conn| {
CommunityView::read(conn, community_id, Some(user_id))
})
-use crate::{blocking, websocket::WebsocketInfo, DbPool, LemmyError};
+use crate::{api::claims::Claims, blocking, websocket::WebsocketInfo, DbPool, LemmyError};
use actix_web::client::Client;
use lemmy_db::{
community::*,
user_view::*,
Crud,
};
+use lemmy_utils::{slur_check, slurs_vec_to_str};
use thiserror::Error;
pub mod claims;
}
Ok(())
}
+
+pub(in crate::api) async fn get_user_from_jwt(
+ jwt: &str,
+ pool: &DbPool,
+) -> Result<User_, LemmyError> {
+ let claims = match Claims::decode(&jwt) {
+ Ok(claims) => claims.claims,
+ Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ };
+ let user_id = claims.id;
+ let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
+ // Check for a site ban
+ if user.banned {
+ return Err(APIError::err("site_ban").into());
+ }
+ Ok(user)
+}
+
+pub(in crate::api) async fn get_user_from_jwt_opt(
+ jwt: &Option<String>,
+ pool: &DbPool,
+) -> Result<Option<User_>, LemmyError> {
+ match jwt {
+ Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
+ None => Ok(None),
+ }
+}
+
+pub(in crate::api) fn check_slurs(text: &str) -> Result<(), APIError> {
+ if let Err(slurs) = slur_check(text) {
+ Err(APIError::err(&slurs_vec_to_str(slurs)))
+ } else {
+ Ok(())
+ }
+}
+pub(in crate::api) fn check_slurs_opt(text: &Option<String>) -> Result<(), APIError> {
+ match text {
+ Some(t) => check_slurs(t),
+ None => Ok(()),
+ }
+}
+pub(in crate::api) async fn check_community_ban(
+ user_id: i32,
+ community_id: i32,
+ pool: &DbPool,
+) -> Result<(), LemmyError> {
+ let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
+ if blocking(pool, is_banned).await? {
+ Err(APIError::err("community_ban").into())
+ } else {
+ Ok(())
+ }
+}
use crate::{
- api::{claims::Claims, is_mod_or_admin, APIError, Oper, Perform},
+ api::{
+ check_community_ban,
+ check_slurs,
+ check_slurs_opt,
+ get_user_from_jwt,
+ get_user_from_jwt_opt,
+ is_mod_or_admin,
+ APIError,
+ Oper,
+ Perform,
+ },
apub::{ApubLikeableType, ApubObjectType},
blocking,
fetch_iframely_and_pictrs_data,
post::*,
post_view::*,
site_view::*,
- user::*,
Crud,
Likeable,
ListingType,
Saveable,
SortType,
};
-use lemmy_utils::{
- is_valid_post_title,
- make_apub_endpoint,
- slur_check,
- slurs_vec_to_str,
- EndpointType,
-};
+use lemmy_utils::{is_valid_post_title, make_apub_endpoint, EndpointType};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use url::Url;
websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, LemmyError> {
let data: &CreatePost = &self.data;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- if let Err(slurs) = slur_check(&data.name) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Some(body) = &data.body {
- if let Err(slurs) = slur_check(body) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
+ check_slurs(&data.name)?;
+ check_slurs_opt(&data.body)?;
if !is_valid_post_title(&data.name) {
return Err(APIError::err("invalid_post_title").into());
}
- let user_id = claims.id;
-
- // Check for a community ban
- 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 = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- if user.banned {
- return Err(APIError::err("site_ban").into());
- }
+ check_community_ban(user.id, data.community_id, pool).await?;
if let Some(url) = data.url.as_ref() {
match Url::parse(url) {
url: data.url.to_owned(),
body: data.body.to_owned(),
community_id: data.community_id,
- creator_id: user_id,
+ creator_id: user.id,
removed: None,
deleted: None,
nsfw: data.nsfw,
// They like their own post by default
let like_form = PostLikeForm {
post_id: inserted_post.id,
- user_id,
+ user_id: user.id,
score: 1,
};
// Refetch the view
let inserted_post_id = inserted_post.id;
let post_view = match blocking(pool, move |conn| {
- PostView::read(conn, inserted_post_id, Some(user_id))
+ PostView::read(conn, inserted_post_id, Some(user.id))
})
.await?
{
websocket_info: Option<WebsocketInfo>,
) -> Result<GetPostResponse, LemmyError> {
let data: &GetPost = &self.data;
-
- let user_id: Option<i32> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => {
- let user_id = claims.claims.id;
- Some(user_id)
- }
- Err(_e) => None,
- },
- None => None,
- };
+ let user = get_user_from_jwt_opt(&data.auth, pool).await?;
+ let user_id = user.map(|u| u.id);
let id = data.id;
let post_view = match blocking(pool, move |conn| PostView::read(conn, id, user_id)).await? {
websocket_info: Option<WebsocketInfo>,
) -> Result<GetPostsResponse, LemmyError> {
let data: &GetPosts = &self.data;
-
- // For logged in users, you need to get back subscribed, and settings
- let user: Option<User_> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => {
- let user_id = claims.claims.id;
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- Some(user)
- }
- Err(_e) => None,
- },
- None => None,
- };
+ let user = get_user_from_jwt_opt(&data.auth, pool).await?;
let user_id = match &user {
Some(user) => Some(user.id),
websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, LemmyError> {
let data: &CreatePostLike = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
// Don't do a downvote if site has downvotes disabled
if data.score == -1 {
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());
- }
+ check_community_ban(user.id, post.community_id, pool).await?;
let like_form = PostLikeForm {
post_id: data.post_id,
- user_id,
+ user_id: user.id,
score: data.score,
};
}
let post_id = data.post_id;
+ let user_id = user.id;
let post_view = match blocking(pool, move |conn| {
PostView::read(conn, post_id, Some(user_id))
})
websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, LemmyError> {
let data: &EditPost = &self.data;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
- if let Err(slurs) = slur_check(&data.name) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Some(body) = &data.body {
- if let Err(slurs) = slur_check(body) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
+ check_slurs(&data.name)?;
+ check_slurs_opt(&data.body)?;
if !is_valid_post_title(&data.name) {
return Err(APIError::err("invalid_post_title").into());
}
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
let edit_id = data.edit_id;
let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??;
- // Check for a community ban
- let community_id = orig_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());
- }
+ check_community_ban(user.id, orig_post.community_id, pool).await?;
// Verify that only the creator can edit
- if !Post::is_post_creator(user_id, orig_post.creator_id) {
+ if !Post::is_post_creator(user.id, orig_post.creator_id) {
return Err(APIError::err("no_post_edit_allowed").into());
}
let edit_id = data.edit_id;
let post_view = blocking(pool, move |conn| {
- PostView::read(conn, edit_id, Some(user_id))
+ PostView::read(conn, edit_id, Some(user.id))
})
.await??;
websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, LemmyError> {
let data: &DeletePost = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let edit_id = data.edit_id;
let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??;
- // 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());
- }
-
- // Check for a community ban
- let community_id = orig_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_community_ban(user.id, orig_post.community_id, pool).await?;
// Verify that only the creator can delete
- if !Post::is_post_creator(user_id, orig_post.creator_id) {
+ if !Post::is_post_creator(user.id, orig_post.creator_id) {
return Err(APIError::err("no_post_edit_allowed").into());
}
// Refetch the post
let edit_id = data.edit_id;
let post_view = blocking(pool, move |conn| {
- PostView::read(conn, edit_id, Some(user_id))
+ PostView::read(conn, edit_id, Some(user.id))
})
.await??;
websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, LemmyError> {
let data: &RemovePost = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let edit_id = data.edit_id;
let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??;
- // 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());
- }
-
- // Check for a community ban
- let community_id = orig_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_community_ban(user.id, orig_post.community_id, pool).await?;
// Verify that only the mods can remove
- is_mod_or_admin(pool, user_id, community_id).await?;
+ is_mod_or_admin(pool, user.id, orig_post.community_id).await?;
// Update the post
let edit_id = data.edit_id;
// Mod tables
let form = ModRemovePostForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
post_id: data.edit_id,
removed: Some(removed),
reason: data.reason.to_owned(),
// Refetch the post
let edit_id = data.edit_id;
+ let user_id = user.id;
let post_view = blocking(pool, move |conn| {
PostView::read(conn, edit_id, Some(user_id))
})
websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, LemmyError> {
let data: &LockPost = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let edit_id = data.edit_id;
let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??;
- // 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());
- }
-
- // Check for a community ban
- let community_id = orig_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_community_ban(user.id, orig_post.community_id, pool).await?;
// Verify that only the mods can lock
- is_mod_or_admin(pool, user_id, community_id).await?;
+ is_mod_or_admin(pool, user.id, orig_post.community_id).await?;
// Update the post
let edit_id = data.edit_id;
// Mod tables
let form = ModLockPostForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
post_id: data.edit_id,
locked: Some(locked),
};
// Refetch the post
let edit_id = data.edit_id;
let post_view = blocking(pool, move |conn| {
- PostView::read(conn, edit_id, Some(user_id))
+ PostView::read(conn, edit_id, Some(user.id))
})
.await??;
websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, LemmyError> {
let data: &StickyPost = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let edit_id = data.edit_id;
let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??;
- // 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());
- }
-
- // Check for a community ban
- let community_id = orig_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_community_ban(user.id, orig_post.community_id, pool).await?;
// Verify that only the mods can sticky
- is_mod_or_admin(pool, user_id, community_id).await?;
+ is_mod_or_admin(pool, user.id, orig_post.community_id).await?;
// Update the post
let edit_id = data.edit_id;
// Mod tables
let form = ModStickyPostForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
post_id: data.edit_id,
stickied: Some(stickied),
};
// Refetch the post
let edit_id = data.edit_id;
let post_view = blocking(pool, move |conn| {
- PostView::read(conn, edit_id, Some(user_id))
+ PostView::read(conn, edit_id, Some(user.id))
})
.await??;
_websocket_info: Option<WebsocketInfo>,
) -> Result<PostResponse, LemmyError> {
let data: &SavePost = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let post_saved_form = PostSavedForm {
post_id: data.post_id,
- user_id,
+ user_id: user.id,
};
if data.save {
}
let post_id = data.post_id;
+ let user_id = user.id;
let post_view = blocking(pool, move |conn| {
PostView::read(conn, post_id, Some(user_id))
})
use super::user::Register;
use crate::{
- api::{claims::Claims, is_admin, APIError, Oper, Perform},
+ api::{
+ check_slurs,
+ check_slurs_opt,
+ get_user_from_jwt,
+ get_user_from_jwt_opt,
+ is_admin,
+ APIError,
+ Oper,
+ Perform,
+ },
apub::fetcher::search_by_apub_id,
blocking,
version,
SearchType,
SortType,
};
-use lemmy_utils::{settings::Settings, slur_check, slurs_vec_to_str};
+use lemmy_utils::settings::Settings;
use log::{debug, info};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
) -> Result<SiteResponse, LemmyError> {
let data: &CreateSite = &self.data;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- if let Err(slurs) = slur_check(&data.name) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Some(description) = &data.description {
- if let Err(slurs) = slur_check(description) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
+ let user = get_user_from_jwt(&data.auth, pool).await?;
- let user_id = claims.id;
+ check_slurs(&data.name)?;
+ check_slurs_opt(&data.description)?;
// Make sure user is an admin
- is_admin(pool, user_id).await?;
+ is_admin(pool, user.id).await?;
let site_form = SiteForm {
name: data.name.to_owned(),
description: data.description.to_owned(),
- creator_id: user_id,
+ creator_id: user.id,
enable_downvotes: data.enable_downvotes,
open_registration: data.open_registration,
enable_nsfw: data.enable_nsfw,
websocket_info: Option<WebsocketInfo>,
) -> Result<SiteResponse, LemmyError> {
let data: &EditSite = &self.data;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- if let Err(slurs) = slur_check(&data.name) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
-
- if let Some(description) = &data.description {
- if let Err(slurs) = slur_check(description) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
- }
-
- let user_id = claims.id;
+ check_slurs(&data.name)?;
+ check_slurs_opt(&data.description)?;
// Make sure user is an admin
- is_admin(pool, user_id).await?;
+ is_admin(pool, user.id).await?;
let found_site = blocking(pool, move |conn| Site::read(conn, 1)).await??;
0
};
- // Giving back your user, if you're logged in
- let my_user: Option<User_> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => {
- let user_id = claims.claims.id;
- let mut user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- user.password_encrypted = "".to_string();
- user.private_key = None;
- user.public_key = None;
- Some(user)
- }
- Err(_e) => None,
- },
- None => None,
- };
+ let my_user = get_user_from_jwt_opt(&data.auth, pool).await?.map(|mut u| {
+ u.password_encrypted = "".to_string();
+ u.private_key = None;
+ u.public_key = None;
+ u
+ });
Ok(GetSiteResponse {
site: site_view,
Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
}
- let user_id: Option<i32> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => {
- let user_id = claims.claims.id;
- Some(user_id)
- }
- Err(_e) => None,
- },
- None => None,
- };
+ let user = get_user_from_jwt_opt(&data.auth, pool).await?;
+ let user_id = user.map(|u| u.id);
let type_ = SearchType::from_str(&data.type_)?;
_websocket_info: Option<WebsocketInfo>,
) -> Result<GetSiteResponse, LemmyError> {
let data: &TransferSite = &self.data;
+ let mut user = get_user_from_jwt(&data.auth, pool).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
- let mut user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
// TODO add a User_::read_safe() for this.
user.password_encrypted = "".to_string();
user.private_key = None;
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 {
+ if read_site.creator_id != user.id {
return Err(APIError::err("not_an_admin").into());
}
// Mod tables
let form = ModAddForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
removed: Some(false),
};
_websocket_info: Option<WebsocketInfo>,
) -> Result<GetSiteConfigResponse, LemmyError> {
let data: &GetSiteConfig = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
// Only let admins read this
- is_admin(pool, user_id).await?;
+ is_admin(pool, user.id).await?;
let config_hjson = Settings::read_config_file()?;
_websocket_info: Option<WebsocketInfo>,
) -> Result<GetSiteConfigResponse, LemmyError> {
let data: &SaveSiteConfig = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
// Only let admins read this
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) {
+ if !admin_ids.contains(&user.id) {
return Err(APIError::err("not_an_admin").into());
}
use crate::{
- api::{claims::Claims, is_admin, APIError, Oper, Perform},
+ api::{
+ check_slurs,
+ claims::Claims,
+ get_user_from_jwt,
+ get_user_from_jwt_opt,
+ is_admin,
+ APIError,
+ Oper,
+ Perform,
+ },
apub::ApubObjectType,
blocking,
captcha_espeak_wav_base64,
remove_slurs,
send_email,
settings::Settings,
- slur_check,
- slurs_vec_to_str,
EndpointType,
};
use log::error;
};
}
- if let Err(slurs) = slur_check(&data.username) {
- return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
- }
+ check_slurs(&data.username)?;
// Make sure there are no admins
let any_admins = blocking(pool, move |conn| {
_websocket_info: Option<WebsocketInfo>,
) -> Result<LoginResponse, LemmyError> {
let data: &SaveUserSettings = &self.data;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
+ let user_id = user.id;
let read_user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
let email = match &data.email {
_websocket_info: Option<WebsocketInfo>,
) -> Result<GetUserDetailsResponse, LemmyError> {
let data: &GetUserDetails = &self.data;
-
- // For logged in users, you need to get back subscribed, and settings
- let user: Option<User_> = match &data.auth {
- Some(auth) => match Claims::decode(&auth) {
- Ok(claims) => {
- let user_id = claims.claims.id;
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- Some(user)
- }
- Err(_e) => None,
- },
- None => None,
- };
-
- let user_id = match &user {
- Some(user) => Some(user.id),
- None => None,
- };
+ let user = get_user_from_jwt_opt(&data.auth, pool).await?;
let show_nsfw = match &user {
Some(user) => user.show_nsfw,
let limit = data.limit;
let saved_only = data.saved_only;
let community_id = data.community_id;
+ let user_id = user.map(|u| u.id);
let (posts, comments) = blocking(pool, move |conn| {
let mut posts_query = PostQueryBuilder::create(conn)
.sort(&sort)
websocket_info: Option<WebsocketInfo>,
) -> Result<AddAdminResponse, LemmyError> {
let data: &AddAdmin = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
// Make sure user is an admin
- is_admin(pool, user_id).await?;
+ is_admin(pool, user.id).await?;
let added = data.added;
let added_user_id = data.user_id;
// Mod tables
let form = ModAddForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
removed: Some(!data.added),
};
websocket_info: Option<WebsocketInfo>,
) -> Result<BanUserResponse, LemmyError> {
let data: &BanUser = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
// Make sure user is an admin
- is_admin(pool, user_id).await?;
+ is_admin(pool, user.id).await?;
let ban = data.ban;
let banned_user_id = data.user_id;
};
let form = ModBanForm {
- mod_user_id: user_id,
+ mod_user_id: user.id,
other_user_id: data.user_id,
reason: data.reason.to_owned(),
banned: Some(data.ban),
_websocket_info: Option<WebsocketInfo>,
) -> Result<GetRepliesResponse, LemmyError> {
let data: &GetReplies = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let sort = SortType::from_str(&data.sort)?;
let page = data.page;
let limit = data.limit;
let unread_only = data.unread_only;
+ let user_id = user.id;
let replies = blocking(pool, move |conn| {
ReplyQueryBuilder::create(conn, user_id)
.sort(&sort)
_websocket_info: Option<WebsocketInfo>,
) -> Result<GetUserMentionsResponse, LemmyError> {
let data: &GetUserMentions = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let sort = SortType::from_str(&data.sort)?;
let page = data.page;
let limit = data.limit;
let unread_only = data.unread_only;
+ let user_id = user.id;
let mentions = blocking(pool, move |conn| {
UserMentionQueryBuilder::create(conn, user_id)
.sort(&sort)
_websocket_info: Option<WebsocketInfo>,
) -> Result<UserMentionResponse, LemmyError> {
let data: &MarkUserMentionAsRead = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let user_mention_id = data.user_mention_id;
let read_user_mention =
blocking(pool, move |conn| UserMention::read(conn, user_mention_id)).await??;
- if user_id != read_user_mention.recipient_id {
+ if user.id != read_user_mention.recipient_id {
return Err(APIError::err("couldnt_update_comment").into());
}
};
let user_mention_id = read_user_mention.id;
+ let user_id = user.id;
let user_mention_view = blocking(pool, move |conn| {
UserMentionView::read(conn, user_mention_id, user_id)
})
_websocket_info: Option<WebsocketInfo>,
) -> Result<GetRepliesResponse, LemmyError> {
let data: &MarkAllAsRead = &self.data;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
+ let user_id = user.id;
let replies = blocking(pool, move |conn| {
ReplyQueryBuilder::create(conn, user_id)
.unread_only(true)
_websocket_info: Option<WebsocketInfo>,
) -> Result<LoginResponse, LemmyError> {
let data: &DeleteAccount = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
// Verify the password
let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false);
}
// Comments
+ let user_id = user.id;
let comments = blocking(pool, move |conn| {
CommentQueryBuilder::create(conn)
.for_creator_id(user_id)
websocket_info: Option<WebsocketInfo>,
) -> Result<PrivateMessageResponse, LemmyError> {
let data: &CreatePrivateMessage = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
let hostname = &format!("https://{}", Settings::get().hostname);
- // 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 content_slurs_removed = remove_slurs(&data.content.to_owned());
let private_message_form = PrivateMessageForm {
content: content_slurs_removed.to_owned(),
- creator_id: user_id,
+ creator_id: user.id,
recipient_id: data.recipient_id,
deleted: None,
read: None,
websocket_info: Option<WebsocketInfo>,
) -> Result<PrivateMessageResponse, LemmyError> {
let data: &EditPrivateMessage = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
- // 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 user = get_user_from_jwt(&data.auth, pool).await?;
// Checking permissions
let edit_id = data.edit_id;
let orig_private_message =
blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??;
- if user_id != orig_private_message.creator_id {
+ if user.id != orig_private_message.creator_id {
return Err(APIError::err("no_private_message_edit_allowed").into());
}
websocket_info: Option<WebsocketInfo>,
) -> Result<PrivateMessageResponse, LemmyError> {
let data: &DeletePrivateMessage = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
- // 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 user = get_user_from_jwt(&data.auth, pool).await?;
// Checking permissions
let edit_id = data.edit_id;
let orig_private_message =
blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??;
- if user_id != orig_private_message.creator_id {
+ if user.id != orig_private_message.creator_id {
return Err(APIError::err("no_private_message_edit_allowed").into());
}
websocket_info: Option<WebsocketInfo>,
) -> Result<PrivateMessageResponse, LemmyError> {
let data: &MarkPrivateMessageAsRead = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
-
- // 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 user = get_user_from_jwt(&data.auth, pool).await?;
// Checking permissions
let edit_id = data.edit_id;
let orig_private_message =
blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??;
- if user_id != orig_private_message.recipient_id {
+ if user.id != orig_private_message.recipient_id {
return Err(APIError::err("couldnt_update_private_message").into());
}
_websocket_info: Option<WebsocketInfo>,
) -> Result<PrivateMessagesResponse, LemmyError> {
let data: &GetPrivateMessages = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
+ let user_id = user.id;
let page = data.page;
let limit = data.limit;
async fn perform(
&self,
- _pool: &DbPool,
+ pool: &DbPool,
websocket_info: Option<WebsocketInfo>,
) -> Result<UserJoinResponse, LemmyError> {
let data: &UserJoin = &self.data;
-
- let claims = match Claims::decode(&data.auth) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
-
- let user_id = claims.id;
+ let user = get_user_from_jwt(&data.auth, pool).await?;
if let Some(ws) = websocket_info {
if let Some(id) = ws.id {
- ws.chatserver.do_send(JoinUserRoom { user_id, id });
+ ws.chatserver.do_send(JoinUserRoom {
+ user_id: user.id,
+ id,
+ });
}
}
- Ok(UserJoinResponse { user_id })
+ Ok(UserJoinResponse { user_id: user.id })
}
}
use crate::{
apub::{
+ check_is_apub_id_valid,
community::do_announce,
extensions::signatures::sign,
insert_activity,
- is_apub_id_valid,
ActorType,
},
request::retry_custom,
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;
- }
+ check_is_apub_id_valid(&to_url)?;
let res = retry_custom(|| async {
let request = client.post(&t).header("Content-Type", "application/json");
use crate::{
api::site::SearchResponse,
apub::{
- is_apub_id_valid,
+ check_is_apub_id_valid,
ActorType,
FromApub,
GroupExt,
where
Response: for<'de> Deserialize<'de>,
{
- if !is_apub_id_valid(&url) {
- return Err(anyhow!("Activitypub uri invalid or blocked: {}", url).into());
- }
+ check_is_apub_id_valid(&url)?;
let timeout = Duration::from_secs(60);
chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> {
let create = Create::from_any_base(activity)?.unwrap();
- dbg!(create.object().as_single_kind_str());
match create.object().as_single_kind_str() {
Some("Page") => receive_create_post(create, client, pool, chat_server).await,
Some("Note") => receive_create_comment(create, client, pool, chat_server).await,
use crate::{
apub::{
+ check_is_apub_id_valid,
extensions::signatures::verify,
- fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
+ fetcher::get_or_fetch_and_upsert_user,
insert_activity,
ActorType,
},
LemmyError,
};
use activitystreams::{
- activity::{Follow, Undo},
+ activity::{ActorAndObject, Follow, Undo},
+ base::AnyBase,
prelude::*,
};
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
Followable,
};
use log::debug;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use std::fmt::Debug;
-#[serde(untagged)]
-#[derive(Deserialize, Debug)]
-pub enum CommunityAcceptedObjects {
- Follow(Follow),
- Undo(Undo),
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
+#[serde(rename_all = "PascalCase")]
+pub enum ValidTypes {
+ Follow,
+ Undo,
}
-impl CommunityAcceptedObjects {
- fn follow(&self) -> Result<Follow, LemmyError> {
- match self {
- CommunityAcceptedObjects::Follow(f) => Ok(f.to_owned()),
- CommunityAcceptedObjects::Undo(u) => {
- Ok(Follow::from_any_base(u.object().as_one().unwrap().to_owned())?.unwrap())
- }
- }
- }
-}
+pub type AcceptedActivities = ActorAndObject<ValidTypes>;
/// Handler for all incoming activities to community inboxes.
pub async fn community_inbox(
request: HttpRequest,
- input: web::Json<CommunityAcceptedObjects>,
+ input: web::Json<AcceptedActivities>,
path: web::Path<String>,
db: DbPoolParam,
client: web::Data<Client>,
_chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> {
- let input = input.into_inner();
+ let activity = input.into_inner();
let path = path.into_inner();
let community = blocking(&db, move |conn| Community::read_from_name(&conn, &path)).await??;
}
debug!(
"Community {} received activity {:?}",
- &community.name, &input
+ &community.name, &activity
);
- let follow = input.follow()?;
- let user_uri = follow.actor()?.as_single_xsd_any_uri().unwrap();
- let community_uri = follow.object().as_single_xsd_any_uri().unwrap();
+ let user_uri = activity.actor()?.as_single_xsd_any_uri().unwrap();
+ check_is_apub_id_valid(user_uri)?;
let user = get_or_fetch_and_upsert_user(&user_uri, &client, &db).await?;
- let community = get_or_fetch_and_upsert_community(community_uri, &client, &db).await?;
verify(&request, &user)?;
- match input {
- CommunityAcceptedObjects::Follow(f) => handle_follow(f, user, community, &client, db).await,
- CommunityAcceptedObjects::Undo(u) => handle_undo_follow(u, user, community, db).await,
+ insert_activity(user.id, activity.clone(), false, &db).await?;
+
+ let any_base = activity.clone().into_any_base()?;
+ let kind = activity.kind().unwrap();
+ match kind {
+ ValidTypes::Follow => handle_follow(any_base, user, community, &client, db).await,
+ ValidTypes::Undo => handle_undo_follow(any_base, user, community, db).await,
}
}
/// Handle a follow request from a remote user, adding it to the local database and returning an
/// Accept activity.
async fn handle_follow(
- follow: Follow,
+ activity: AnyBase,
user: User_,
community: Community,
client: &Client,
db: DbPoolParam,
) -> Result<HttpResponse, LemmyError> {
- insert_activity(user.id, follow.clone(), false, &db).await?;
-
+ let follow = Follow::from_any_base(activity)?.unwrap();
let community_follower_form = CommunityFollowerForm {
community_id: community.id,
user_id: user.id,
}
async fn handle_undo_follow(
- undo: Undo,
+ activity: AnyBase,
user: User_,
community: Community,
db: DbPoolParam,
) -> Result<HttpResponse, LemmyError> {
- insert_activity(user.id, undo, false, &db).await?;
+ let _undo = Undo::from_any_base(activity)?.unwrap();
let community_follower_form = CommunityFollowerForm {
community_id: community.id,
use crate::{
apub::{
+ check_is_apub_id_valid,
community::do_announce,
extensions::signatures::verify,
fetcher::{
use actix_web::{client::Client, web, HttpRequest, HttpResponse};
use lemmy_db::user::User_;
use log::debug;
-use serde::Serialize;
+use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use url::Url;
-#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)]
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
pub enum ValidTypes {
Create,
debug!("Shared inbox received activity: {}", json);
let sender = &activity.actor()?.to_owned().single_xsd_any_uri().unwrap();
-
// TODO: pass this actor in instead of using get_user_from_activity()
let actor = get_or_fetch_and_upsert_actor(sender, &client, &pool).await?;
+
+ let community = get_community_id_from_activity(&activity).await;
+
+ check_is_apub_id_valid(sender)?;
+ check_is_apub_id_valid(&community)?;
verify(&request, actor.as_ref())?;
insert_activity(actor.user_id(), activity.clone(), false, &pool).await?;
let any_base = activity.clone().into_any_base()?;
let kind = activity.kind().unwrap();
- dbg!(kind);
match kind {
ValidTypes::Announce => receive_announce(any_base, &client, &pool, chat_server).await,
ValidTypes::Create => receive_create(any_base, &client, &pool, chat_server).await,
get_or_fetch_and_upsert_user(&user_uri, client, pool).await
}
+pub(in crate::apub::inbox) async fn get_community_id_from_activity<T, A>(activity: &T) -> Url
+where
+ T: AsBase<A> + ActorAndObjectRef + AsObject<A>,
+{
+ let cc = activity.cc().unwrap();
+ let cc = cc.as_many().unwrap();
+ cc.first().unwrap().as_xsd_any_uri().unwrap().to_owned()
+}
+
pub(in crate::apub::inbox) async fn announce_if_community_is_local<T, Kind>(
activity: T,
user: &User_,
use crate::{
api::user::PrivateMessageResponse,
apub::{
+ check_is_apub_id_valid,
extensions::signatures::verify,
- fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
+ fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_community},
insert_activity,
FromApub,
},
LemmyError,
};
use activitystreams::{
- activity::{Accept, Create, Delete, Undo, Update},
+ activity::{Accept, ActorAndObject, Create, Delete, Undo, Update},
+ base::AnyBase,
object::Note,
prelude::*,
};
Followable,
};
use log::debug;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use std::fmt::Debug;
-#[serde(untagged)]
-#[derive(Deserialize, Debug)]
-pub enum UserAcceptedObjects {
- Accept(Box<Accept>),
- Create(Box<Create>),
- Update(Box<Update>),
- Delete(Box<Delete>),
- Undo(Box<Undo>),
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
+#[serde(rename_all = "PascalCase")]
+pub enum ValidTypes {
+ Accept,
+ Create,
+ Update,
+ Delete,
+ Undo,
}
+pub type AcceptedActivities = ActorAndObject<ValidTypes>;
+
/// Handler for all incoming activities to user inboxes.
pub async fn user_inbox(
request: HttpRequest,
- input: web::Json<UserAcceptedObjects>,
+ input: web::Json<AcceptedActivities>,
path: web::Path<String>,
client: web::Data<Client>,
- db: DbPoolParam,
+ pool: DbPoolParam,
chat_server: ChatServerParam,
) -> 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 activity = input.into_inner();
let username = path.into_inner();
- debug!("User {} received activity: {:?}", &username, &input);
+ debug!("User {} received activity: {:?}", &username, &activity);
+
+ let actor_uri = activity.actor()?.as_single_xsd_any_uri().unwrap();
+
+ check_is_apub_id_valid(actor_uri)?;
+
+ let actor = get_or_fetch_and_upsert_actor(actor_uri, &client, &pool).await?;
+ verify(&request, actor.as_ref())?;
- match input {
- UserAcceptedObjects::Accept(a) => receive_accept(*a, &request, &username, &client, &db).await,
- UserAcceptedObjects::Create(c) => {
- receive_create_private_message(*c, &request, &client, &db, chat_server).await
+ insert_activity(actor.user_id(), activity.clone(), false, &pool).await?;
+
+ let any_base = activity.clone().into_any_base()?;
+ let kind = activity.kind().unwrap();
+ match kind {
+ ValidTypes::Accept => receive_accept(any_base, username, &client, &pool).await,
+ ValidTypes::Create => {
+ receive_create_private_message(any_base, &client, &pool, chat_server).await
}
- UserAcceptedObjects::Update(u) => {
- receive_update_private_message(*u, &request, &client, &db, chat_server).await
+ ValidTypes::Update => {
+ receive_update_private_message(any_base, &client, &pool, chat_server).await
}
- UserAcceptedObjects::Delete(d) => {
- receive_delete_private_message(*d, &request, &client, &db, chat_server).await
+ ValidTypes::Delete => {
+ receive_delete_private_message(any_base, &client, &pool, chat_server).await
}
- UserAcceptedObjects::Undo(u) => {
- receive_undo_delete_private_message(*u, &request, &client, &db, chat_server).await
+ ValidTypes::Undo => {
+ receive_undo_delete_private_message(any_base, &client, &pool, chat_server).await
}
}
}
/// Handle accepted follows.
async fn receive_accept(
- accept: Accept,
- request: &HttpRequest,
- username: &str,
+ activity: AnyBase,
+ username: String,
client: &Client,
pool: &DbPool,
) -> Result<HttpResponse, LemmyError> {
+ let accept = Accept::from_any_base(activity)?.unwrap();
let community_uri = accept.actor()?.to_owned().single_xsd_any_uri().unwrap();
let community = get_or_fetch_and_upsert_community(&community_uri, client, pool).await?;
- verify(request, &community)?;
- let username = username.to_owned();
let user = blocking(pool, move |conn| User_::read_from_name(conn, &username)).await??;
- insert_activity(community.creator_id, accept, false, pool).await?;
-
// Now you need to add this to the community follower
let community_follower_form = CommunityFollowerForm {
community_id: community.id,
// This will fail if they're already a follower
blocking(pool, move |conn| {
- CommunityFollower::follow(conn, &community_follower_form)
+ CommunityFollower::follow(conn, &community_follower_form).ok()
})
- .await??;
+ .await?;
// TODO: make sure that we actually requested a follow
Ok(HttpResponse::Ok().finish())
}
async fn receive_create_private_message(
- create: Create,
- request: &HttpRequest,
+ activity: AnyBase,
client: &Client,
pool: &DbPool,
chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> {
- let user_uri = &create.actor()?.to_owned().single_xsd_any_uri().unwrap();
+ let create = Create::from_any_base(activity)?.unwrap();
let note = Note::from_any_base(create.object().as_one().unwrap().to_owned())?.unwrap();
- let user = get_or_fetch_and_upsert_user(user_uri, client, pool).await?;
- verify(request, &user)?;
-
- insert_activity(user.id, create, false, pool).await?;
-
let private_message = PrivateMessageForm::from_apub(¬e, client, pool).await?;
let inserted_private_message = blocking(pool, move |conn| {
}
async fn receive_update_private_message(
- update: Update,
- request: &HttpRequest,
+ activity: AnyBase,
client: &Client,
pool: &DbPool,
chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> {
- let user_uri = &update.actor()?.to_owned().single_xsd_any_uri().unwrap();
+ let update = Update::from_any_base(activity)?.unwrap();
let note = Note::from_any_base(update.object().as_one().unwrap().to_owned())?.unwrap();
- let user = get_or_fetch_and_upsert_user(&user_uri, client, pool).await?;
- verify(request, &user)?;
-
- insert_activity(user.id, update, false, pool).await?;
-
let private_message_form = PrivateMessageForm::from_apub(¬e, client, pool).await?;
let private_message_ap_id = private_message_form.ap_id.clone();
}
async fn receive_delete_private_message(
- delete: Delete,
- request: &HttpRequest,
+ activity: AnyBase,
client: &Client,
pool: &DbPool,
chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> {
- let user_uri = &delete.actor()?.to_owned().single_xsd_any_uri().unwrap();
+ let delete = Delete::from_any_base(activity)?.unwrap();
let note = Note::from_any_base(delete.object().as_one().unwrap().to_owned())?.unwrap();
- let user = get_or_fetch_and_upsert_user(&user_uri, client, pool).await?;
- verify(request, &user)?;
-
- insert_activity(user.id, delete, false, pool).await?;
-
let private_message_form = PrivateMessageForm::from_apub(¬e, client, pool).await?;
let private_message_ap_id = private_message_form.ap_id;
}
async fn receive_undo_delete_private_message(
- undo: Undo,
- request: &HttpRequest,
+ activity: AnyBase,
client: &Client,
pool: &DbPool,
chat_server: ChatServerParam,
) -> Result<HttpResponse, LemmyError> {
+ let undo = Undo::from_any_base(activity)?.unwrap();
let delete = Delete::from_any_base(undo.object().as_one().unwrap().to_owned())?.unwrap();
let note = Note::from_any_base(delete.object().as_one().unwrap().to_owned())?.unwrap();
- let user_uri = &delete.actor()?.to_owned().single_xsd_any_uri().unwrap();
-
- let user = get_or_fetch_and_upsert_user(&user_uri, client, pool).await?;
- verify(request, &user)?;
-
- insert_activity(user.id, delete, false, pool).await?;
let private_message = PrivateMessageForm::from_apub(¬e, client, pool).await?;
}
// 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);
+fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
if apub_id.scheme() != get_apub_protocol_string() {
- debug!("invalid scheme: {:?}", apub_id.scheme());
- return false;
+ return Err(anyhow!("invalid apub id scheme: {:?}", apub_id.scheme()).into());
}
- let allowed_instances: Vec<String> = Settings::get()
+ let mut allowed_instances: Vec<String> = Settings::get()
.federation
.allowed_instances
.split(',')
.map(|d| d.to_string())
.collect();
+ // need to allow this explicitly because apub activities might contain objects from our local
+ // instance. replace is needed to remove the port in our federation test setup.
+ let settings = Settings::get();
+ let local_instance = settings.hostname.split(':').collect::<Vec<&str>>();
+ allowed_instances.push(local_instance.first().unwrap().to_string());
+
match apub_id.domain() {
Some(d) => {
let contains = allowed_instances.contains(&d.to_owned());
if !contains {
- debug!("{} not in {:?}", d, allowed_instances);
+ return Err(anyhow!("{} not in federation allowlist", d).into());
}
- contains
- }
- None => {
- debug!("missing domain");
- false
+ Ok(())
}
+ None => Err(anyhow!("federation allowlist is empty").into()),
}
}
"license": "AGPL-3.0-or-later",
"main": "index.js",
"scripts": {
- "api-test": "jest src/api_tests/api.spec.ts",
+ "api-test": "jest src/api_tests/ -i --verbose",
"build": "node fuse prod",
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src",
"prebuild": "node generate_translations.js",
+++ /dev/null
-import fetch from 'node-fetch';
-
-import {
- LoginForm,
- LoginResponse,
- PostForm,
- DeletePostForm,
- RemovePostForm,
- StickyPostForm,
- LockPostForm,
- PostResponse,
- SearchResponse,
- FollowCommunityForm,
- CommunityResponse,
- GetFollowedCommunitiesResponse,
- GetPostResponse,
- CommentForm,
- DeleteCommentForm,
- RemoveCommentForm,
- CommentResponse,
- CommunityForm,
- DeleteCommunityForm,
- RemoveCommunityForm,
- GetCommunityResponse,
- CommentLikeForm,
- CreatePostLikeForm,
- PrivateMessageForm,
- EditPrivateMessageForm,
- DeletePrivateMessageForm,
- PrivateMessageResponse,
- PrivateMessagesResponse,
- GetUserMentionsResponse,
-} from '../interfaces';
-
-let lemmyAlphaUrl = 'http://localhost:8540';
-let lemmyAlphaApiUrl = `${lemmyAlphaUrl}/api/v1`;
-let lemmyAlphaAuth: string;
-
-let lemmyBetaUrl = 'http://localhost:8550';
-let lemmyBetaApiUrl = `${lemmyBetaUrl}/api/v1`;
-let lemmyBetaAuth: string;
-
-let lemmyGammaUrl = 'http://localhost:8560';
-let lemmyGammaApiUrl = `${lemmyGammaUrl}/api/v1`;
-let lemmyGammaAuth: string;
-
-// Workaround for tests being run before beforeAll() is finished
-// https://github.com/facebook/jest/issues/9527#issuecomment-592406108
-describe('main', () => {
- beforeAll(async () => {
- console.log('Logging in as lemmy_alpha');
- let form: LoginForm = {
- username_or_email: 'lemmy_alpha',
- password: 'lemmy',
- };
-
- let res: LoginResponse = await fetch(`${lemmyAlphaApiUrl}/user/login`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(form),
- }).then(d => d.json());
-
- lemmyAlphaAuth = res.jwt;
-
- console.log('Logging in as lemmy_beta');
- let formB = {
- username_or_email: 'lemmy_beta',
- password: 'lemmy',
- };
-
- let resB: LoginResponse = await fetch(`${lemmyBetaApiUrl}/user/login`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(formB),
- }).then(d => d.json());
-
- lemmyBetaAuth = resB.jwt;
-
- console.log('Logging in as lemmy_gamma');
- let formC = {
- username_or_email: 'lemmy_gamma',
- password: 'lemmy',
- };
-
- let resG: LoginResponse = await fetch(`${lemmyGammaApiUrl}/user/login`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(formC),
- }).then(d => d.json());
-
- lemmyGammaAuth = resG.jwt;
- });
-
- describe('post_search', () => {
- test('Create test post on alpha and fetch it on beta', async () => {
- let name = 'A jest test post';
- let postForm: PostForm = {
- name,
- auth: lemmyAlphaAuth,
- community_id: 2,
- nsfw: false,
- };
-
- let createPostRes: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(postForm),
- }
- ).then(d => d.json());
- expect(createPostRes.post.name).toBe(name);
-
- let searchUrl = `${lemmyBetaApiUrl}/search?q=${createPostRes.post.ap_id}&type_=All&sort=TopAll`;
- let searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- // TODO: check more fields
- expect(searchResponse.posts[0].name).toBe(name);
- });
- });
-
- 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
- // Use short-hand search url
- let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`;
-
- let searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(searchResponse.communities[0].name).toBe('main');
-
- let followForm: FollowCommunityForm = {
- community_id: searchResponse.communities[0].id,
- follow: true,
- auth: lemmyAlphaAuth,
- };
-
- let followRes: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followForm),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followRes.community.local).toBe(false);
- expect(followRes.community.name).toBe('main');
-
- // Check that you are subscribed to it locally
- let followedCommunitiesUrl = `${lemmyAlphaApiUrl}/user/followed_communities?&auth=${lemmyAlphaAuth}`;
- let followedCommunitiesRes: GetFollowedCommunitiesResponse = await fetch(
- followedCommunitiesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(followedCommunitiesRes.communities[1].community_local).toBe(false);
-
- // Test out unfollowing
- let unfollowForm: FollowCommunityForm = {
- community_id: searchResponse.communities[0].id,
- follow: false,
- auth: lemmyAlphaAuth,
- };
-
- let unfollowRes: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unfollowForm),
- }
- ).then(d => d.json());
- expect(unfollowRes.community.local).toBe(false);
-
- // Check that you are unsubscribed to it locally
- let followedCommunitiesResAgain: GetFollowedCommunitiesResponse = await fetch(
- followedCommunitiesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(followedCommunitiesResAgain.communities.length).toBe(1);
-
- // Follow again, for other tests
- let followResAgain: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followForm),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followResAgain.community.local).toBe(false);
- expect(followResAgain.community.name).toBe('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 searchResponseG: SearchResponse = await fetch(searchUrlG, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(searchResponseG.communities[0].name).toBe('main');
-
- let followFormG: FollowCommunityForm = {
- community_id: searchResponseG.communities[0].id,
- follow: true,
- auth: lemmyGammaAuth,
- };
-
- let followResG: CommunityResponse = await fetch(
- `${lemmyGammaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followFormG),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followResG.community.local).toBe(false);
- expect(followResG.community.name).toBe('main');
-
- // Check that you are subscribed to it locally
- let followedCommunitiesUrlG = `${lemmyGammaApiUrl}/user/followed_communities?&auth=${lemmyGammaAuth}`;
- let followedCommunitiesResG: GetFollowedCommunitiesResponse = await fetch(
- followedCommunitiesUrlG,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(followedCommunitiesResG.communities[1].community_local).toBe(
- false
- );
- });
- });
-
- describe('create test post', () => {
- test('/u/lemmy_alpha creates a post on /c/lemmy_beta/main, its on both instances', async () => {
- let name = 'A jest test federated post';
- let postForm: PostForm = {
- name,
- auth: lemmyAlphaAuth,
- community_id: 3,
- nsfw: false,
- };
-
- let createResponse: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(postForm),
- }
- ).then(d => d.json());
-
- let unlikePostForm: CreatePostLikeForm = {
- post_id: createResponse.post.id,
- score: 0,
- auth: lemmyAlphaAuth,
- };
- expect(createResponse.post.name).toBe(name);
- expect(createResponse.post.community_local).toBe(false);
- expect(createResponse.post.creator_local).toBe(true);
- expect(createResponse.post.score).toBe(1);
-
- let unlikePostRes: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post/like`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unlikePostForm),
- }
- ).then(d => d.json());
- expect(unlikePostRes.post.score).toBe(0);
-
- let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.post.name).toBe(name);
- expect(getPostRes.post.community_local).toBe(true);
- expect(getPostRes.post.creator_local).toBe(false);
- expect(getPostRes.post.score).toBe(0);
- });
- });
-
- describe('update test post', () => {
- test('/u/lemmy_alpha updates a post on /c/lemmy_beta/main, the update is on both', async () => {
- let name = 'A jest test federated post, updated';
- let postForm: PostForm = {
- name,
- edit_id: 2,
- auth: lemmyAlphaAuth,
- community_id: 3,
- nsfw: false,
- };
-
- let updateResponse: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(postForm),
- }
- ).then(d => d.json());
-
- expect(updateResponse.post.name).toBe(name);
- expect(updateResponse.post.community_local).toBe(false);
- expect(updateResponse.post.creator_local).toBe(true);
-
- let stickyPostForm: StickyPostForm = {
- edit_id: 2,
- stickied: true,
- auth: lemmyAlphaAuth,
- };
-
- let stickyRes: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post/sticky`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(stickyPostForm),
- }
- ).then(d => d.json());
-
- expect(stickyRes.post.name).toBe(name);
- expect(stickyRes.post.stickied).toBe(true);
-
- // Fetch from B
- let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.post.name).toBe(name);
- expect(getPostRes.post.community_local).toBe(true);
- expect(getPostRes.post.creator_local).toBe(false);
- expect(getPostRes.post.stickied).toBe(true);
-
- let lockPostForm: LockPostForm = {
- edit_id: 2,
- locked: true,
- auth: lemmyAlphaAuth,
- };
-
- let lockedRes: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post/lock`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(lockPostForm),
- }
- ).then(d => d.json());
-
- expect(lockedRes.post.name).toBe(name);
- expect(lockedRes.post.locked).toBe(true);
-
- // Fetch from B to make sure its locked
- getPostRes = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostRes.post.locked).toBe(true);
-
- // Create a test comment on a locked post, it should be undefined
- // since it shouldn't get created.
- let content = 'A rejected comment on a locked post';
- let commentForm: CommentForm = {
- content,
- post_id: 2,
- auth: lemmyAlphaAuth,
- };
-
- let createResponse: CommentResponse = await fetch(
- `${lemmyAlphaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(commentForm),
- }
- ).then(d => d.json());
-
- expect(createResponse['error']).toBe('locked');
-
- // Unlock the post for later actions
- let unlockPostForm: LockPostForm = {
- edit_id: 2,
- locked: false,
- auth: lemmyAlphaAuth,
- };
-
- let unlockedRes: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post/lock`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unlockPostForm),
- }
- ).then(d => d.json());
-
- expect(unlockedRes.post.name).toBe(name);
- expect(unlockedRes.post.locked).toBe(false);
- });
- });
-
- describe('create test comment', () => {
- test('/u/lemmy_alpha creates a comment on /c/lemmy_beta/main, its on both instances', async () => {
- let content = 'A jest test federated comment';
- let commentForm: CommentForm = {
- content,
- post_id: 2,
- auth: lemmyAlphaAuth,
- };
-
- let createResponse: CommentResponse = await fetch(
- `${lemmyAlphaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(commentForm),
- }
- ).then(d => d.json());
-
- expect(createResponse.comment.content).toBe(content);
- expect(createResponse.comment.community_local).toBe(false);
- expect(createResponse.comment.creator_local).toBe(true);
- expect(createResponse.comment.score).toBe(1);
-
- // Do an unlike, to test it
- let unlikeCommentForm: CommentLikeForm = {
- comment_id: createResponse.comment.id,
- score: 0,
- auth: lemmyAlphaAuth,
- };
-
- let unlikeCommentRes: CommentResponse = await fetch(
- `${lemmyAlphaApiUrl}/comment/like`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unlikeCommentForm),
- }
- ).then(d => d.json());
-
- expect(unlikeCommentRes.comment.score).toBe(0);
-
- let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.comments[0].content).toBe(content);
- expect(getPostRes.comments[0].community_local).toBe(true);
- expect(getPostRes.comments[0].creator_local).toBe(false);
- expect(getPostRes.comments[0].score).toBe(0);
-
- // Now do beta replying to that comment, as a child comment
- let contentBeta = 'A child federated comment from beta';
- let commentFormBeta: CommentForm = {
- content: contentBeta,
- post_id: getPostRes.post.id,
- parent_id: getPostRes.comments[0].id,
- auth: lemmyBetaAuth,
- };
-
- let createResponseBeta: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(commentFormBeta),
- }
- ).then(d => d.json());
-
- expect(createResponseBeta.comment.content).toBe(contentBeta);
- expect(createResponseBeta.comment.community_local).toBe(true);
- expect(createResponseBeta.comment.creator_local).toBe(true);
- expect(createResponseBeta.comment.parent_id).toBe(1);
- expect(createResponseBeta.comment.score).toBe(1);
-
- // Make sure lemmy alpha sees that new child comment from beta
- let getPostUrlAlpha = `${lemmyAlphaApiUrl}/post?id=2`;
- let getPostResAlpha: GetPostResponse = await fetch(getPostUrlAlpha, {
- method: 'GET',
- }).then(d => d.json());
-
- // The newest show up first
- expect(getPostResAlpha.comments[0].content).toBe(contentBeta);
- expect(getPostResAlpha.comments[0].community_local).toBe(false);
- expect(getPostResAlpha.comments[0].creator_local).toBe(false);
- expect(getPostResAlpha.comments[0].score).toBe(1);
-
- // 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 mentionCommentForm: CommentForm = {
- content: mentionContent,
- post_id: 2,
- parent_id: createResponse.comment.id,
- auth: lemmyAlphaAuth,
- };
-
- let createMentionRes: CommentResponse = await fetch(
- `${lemmyAlphaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(mentionCommentForm),
- }
- ).then(d => d.json());
-
- expect(createMentionRes.comment.content).toBe(mentionContent);
- expect(createMentionRes.comment.community_local).toBe(false);
- expect(createMentionRes.comment.creator_local).toBe(true);
- expect(createMentionRes.comment.score).toBe(1);
-
- // Make sure lemmy beta sees that new mention
- let getMentionUrl = `${lemmyBetaApiUrl}/user/mention?sort=New&unread_only=false&auth=${lemmyBetaAuth}`;
- let getMentionsRes: GetUserMentionsResponse = await fetch(getMentionUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- // The newest show up first
- expect(getMentionsRes.mentions[0].content).toBe(mentionContent);
- expect(getMentionsRes.mentions[0].community_local).toBe(true);
- expect(getMentionsRes.mentions[0].creator_local).toBe(false);
- expect(getMentionsRes.mentions[0].score).toBe(1);
- });
- });
-
- describe('update test comment', () => {
- test('/u/lemmy_alpha updates a comment on /c/lemmy_beta/main, its on both instances', async () => {
- let content = 'A jest test federated comment update';
- let commentForm: CommentForm = {
- content,
- post_id: 2,
- edit_id: 1,
- auth: lemmyAlphaAuth,
- creator_id: 2,
- };
-
- let updateResponse: CommentResponse = await fetch(
- `${lemmyAlphaApiUrl}/comment`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(commentForm),
- }
- ).then(d => d.json());
-
- expect(updateResponse.comment.content).toBe(content);
- expect(updateResponse.comment.community_local).toBe(false);
- expect(updateResponse.comment.creator_local).toBe(true);
-
- let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.comments[2].content).toBe(content);
- expect(getPostRes.comments[2].community_local).toBe(true);
- expect(getPostRes.comments[2].creator_local).toBe(false);
- });
- });
-
- describe('federated comment like', () => {
- test('/u/lemmy_beta likes a comment from /u/lemmy_alpha, the like is on both instances', async () => {
- // Do a like, to test it (its also been unliked, so its at 0)
- let likeCommentForm: CommentLikeForm = {
- comment_id: 1,
- score: 1,
- auth: lemmyBetaAuth,
- };
-
- let likeCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment/like`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(likeCommentForm),
- }
- ).then(d => d.json());
-
- expect(likeCommentRes.comment.score).toBe(1);
-
- let getPostUrl = `${lemmyAlphaApiUrl}/post?id=2`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.comments[2].score).toBe(1);
- });
- });
-
- describe('delete things', () => {
- test('/u/lemmy_beta deletes and undeletes a federated comment, post, and community, lemmy_alpha sees its deleted.', async () => {
- // Create a test community
- let communityName = 'test_community';
- let communityForm: CommunityForm = {
- name: communityName,
- title: communityName,
- category_id: 1,
- nsfw: false,
- auth: lemmyBetaAuth,
- };
-
- let createCommunityRes: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(communityForm),
- }
- ).then(d => d.json());
-
- 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 searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- let communityOnAlphaId = searchResponse.communities[0].id;
-
- // Follow it
- let followForm: FollowCommunityForm = {
- community_id: communityOnAlphaId,
- follow: true,
- auth: lemmyAlphaAuth,
- };
-
- let followRes: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followForm),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followRes.community.local).toBe(false);
- expect(followRes.community.name).toBe(communityName);
-
- // Lemmy beta creates a test post
- let postName = 'A jest test post with delete';
- let createPostForm: PostForm = {
- name: postName,
- auth: lemmyBetaAuth,
- community_id: createCommunityRes.community.id,
- nsfw: false,
- };
-
- let createPostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createPostForm),
- }).then(d => d.json());
- expect(createPostRes.post.name).toBe(postName);
-
- // Lemmy beta creates a test comment
- let commentContent = 'A jest test federated comment with delete';
- let createCommentForm: CommentForm = {
- content: commentContent,
- post_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- };
-
- let createCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createCommentForm),
- }
- ).then(d => d.json());
-
- expect(createCommentRes.comment.content).toBe(commentContent);
-
- // lemmy_beta deletes the comment
- let deleteCommentForm: DeleteCommentForm = {
- edit_id: createCommentRes.comment.id,
- deleted: true,
- auth: lemmyBetaAuth,
- };
-
- let deleteCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment/delete`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(deleteCommentForm),
- }
- ).then(d => d.json());
- expect(deleteCommentRes.comment.deleted).toBe(true);
-
- // lemmy_alpha sees that the comment is deleted
- let getPostUrl = `${lemmyAlphaApiUrl}/post?id=3`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostRes.comments[0].deleted).toBe(true);
-
- // lemmy_beta undeletes the comment
- let undeleteCommentForm: DeleteCommentForm = {
- edit_id: createCommentRes.comment.id,
- deleted: false,
- auth: lemmyBetaAuth,
- };
-
- let undeleteCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment/delete`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(undeleteCommentForm),
- }
- ).then(d => d.json());
- expect(undeleteCommentRes.comment.deleted).toBe(false);
-
- // lemmy_alpha sees that the comment is undeleted
- let getPostUndeleteRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostUndeleteRes.comments[0].deleted).toBe(false);
-
- // lemmy_beta deletes the post
- let deletePostForm: DeletePostForm = {
- edit_id: createPostRes.post.id,
- deleted: true,
- auth: lemmyBetaAuth,
- };
-
- let deletePostRes: PostResponse = await fetch(
- `${lemmyBetaApiUrl}/post/delete`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(deletePostForm),
- }
- ).then(d => d.json());
- expect(deletePostRes.post.deleted).toBe(true);
-
- // Make sure lemmy_alpha sees the post is deleted
- let getPostResAgain: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostResAgain.post.deleted).toBe(true);
-
- // lemmy_beta undeletes the post
- let undeletePostForm: DeletePostForm = {
- edit_id: createPostRes.post.id,
- deleted: false,
- auth: lemmyBetaAuth,
- };
-
- let undeletePostRes: PostResponse = await fetch(
- `${lemmyBetaApiUrl}/post/delete`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(undeletePostForm),
- }
- ).then(d => d.json());
- expect(undeletePostRes.post.deleted).toBe(false);
-
- // Make sure lemmy_alpha sees the post is undeleted
- let getPostResAgainTwo: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostResAgainTwo.post.deleted).toBe(false);
-
- // lemmy_beta deletes the community
- let deleteCommunityForm: DeleteCommunityForm = {
- edit_id: createCommunityRes.community.id,
- deleted: true,
- auth: lemmyBetaAuth,
- };
-
- let deleteResponse: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community/delete`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(deleteCommunityForm),
- }
- ).then(d => d.json());
-
- // Make sure the delete went through
- expect(deleteResponse.community.deleted).toBe(true);
-
- // Re-get it from alpha, make sure its deleted there too
- let getCommunityUrl = `${lemmyAlphaApiUrl}/community?id=${communityOnAlphaId}&auth=${lemmyAlphaAuth}`;
- let getCommunityRes: GetCommunityResponse = await fetch(getCommunityUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getCommunityRes.community.deleted).toBe(true);
-
- // lemmy_beta undeletes the community
- let undeleteCommunityForm: DeleteCommunityForm = {
- edit_id: createCommunityRes.community.id,
- deleted: false,
- auth: lemmyBetaAuth,
- };
-
- let undeleteCommunityRes: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community/delete`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(undeleteCommunityForm),
- }
- ).then(d => d.json());
-
- // Make sure the delete went through
- expect(undeleteCommunityRes.community.deleted).toBe(false);
-
- // Re-get it from alpha, make sure its deleted there too
- let getCommunityResAgain: GetCommunityResponse = await fetch(
- getCommunityUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
- expect(getCommunityResAgain.community.deleted).toBe(false);
- });
- });
-
- describe('remove things', () => {
- test('/u/lemmy_beta removes and unremoves a federated comment, post, and community, lemmy_alpha sees its removed.', async () => {
- // Create a test community
- let communityName = 'test_community_rem';
- let communityForm: CommunityForm = {
- name: communityName,
- title: communityName,
- category_id: 1,
- nsfw: false,
- auth: lemmyBetaAuth,
- };
-
- let createCommunityRes: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(communityForm),
- }
- ).then(d => d.json());
-
- 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 searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- let communityOnAlphaId = searchResponse.communities[0].id;
-
- // Follow it
- let followForm: FollowCommunityForm = {
- community_id: communityOnAlphaId,
- follow: true,
- auth: lemmyAlphaAuth,
- };
-
- let followRes: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followForm),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followRes.community.local).toBe(false);
- expect(followRes.community.name).toBe(communityName);
-
- // Lemmy beta creates a test post
- let postName = 'A jest test post with remove';
- let createPostForm: PostForm = {
- name: postName,
- auth: lemmyBetaAuth,
- community_id: createCommunityRes.community.id,
- nsfw: false,
- };
-
- let createPostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createPostForm),
- }).then(d => d.json());
- expect(createPostRes.post.name).toBe(postName);
-
- // Lemmy beta creates a test comment
- let commentContent = 'A jest test federated comment with remove';
- let createCommentForm: CommentForm = {
- content: commentContent,
- post_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- };
-
- let createCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createCommentForm),
- }
- ).then(d => d.json());
-
- expect(createCommentRes.comment.content).toBe(commentContent);
-
- // lemmy_beta removes the comment
- let removeCommentForm: RemoveCommentForm = {
- edit_id: createCommentRes.comment.id,
- removed: true,
- auth: lemmyBetaAuth,
- };
-
- let removeCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment/remove`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(removeCommentForm),
- }
- ).then(d => d.json());
- expect(removeCommentRes.comment.removed).toBe(true);
-
- // lemmy_alpha sees that the comment is removed
- let getPostUrl = `${lemmyAlphaApiUrl}/post?id=4`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostRes.comments[0].removed).toBe(true);
-
- // lemmy_beta undeletes the comment
- let unremoveCommentForm: RemoveCommentForm = {
- edit_id: createCommentRes.comment.id,
- removed: false,
- auth: lemmyBetaAuth,
- };
-
- let unremoveCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment/remove`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unremoveCommentForm),
- }
- ).then(d => d.json());
- expect(unremoveCommentRes.comment.removed).toBe(false);
-
- // lemmy_alpha sees that the comment is undeleted
- let getPostUnremoveRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostUnremoveRes.comments[0].removed).toBe(false);
-
- // lemmy_beta deletes the post
- let removePostForm: RemovePostForm = {
- edit_id: createPostRes.post.id,
- removed: true,
- auth: lemmyBetaAuth,
- };
-
- let removePostRes: PostResponse = await fetch(
- `${lemmyBetaApiUrl}/post/remove`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(removePostForm),
- }
- ).then(d => d.json());
- expect(removePostRes.post.removed).toBe(true);
-
- // Make sure lemmy_alpha sees the post is deleted
- let getPostResAgain: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostResAgain.post.removed).toBe(true);
-
- // lemmy_beta unremoves the post
- let unremovePostForm: RemovePostForm = {
- edit_id: createPostRes.post.id,
- removed: false,
- auth: lemmyBetaAuth,
- };
-
- let unremovePostRes: PostResponse = await fetch(
- `${lemmyBetaApiUrl}/post/remove`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unremovePostForm),
- }
- ).then(d => d.json());
- expect(unremovePostRes.post.removed).toBe(false);
-
- // Make sure lemmy_alpha sees the post is unremoved
- let getPostResAgainTwo: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
- expect(getPostResAgainTwo.post.removed).toBe(false);
-
- // lemmy_beta removes the community
- let removeCommunityForm: RemoveCommunityForm = {
- edit_id: createCommunityRes.community.id,
- removed: true,
- auth: lemmyBetaAuth,
- };
-
- let removeCommunityRes: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community/remove`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(removeCommunityForm),
- }
- ).then(d => d.json());
-
- // Make sure the remove went through
- expect(removeCommunityRes.community.removed).toBe(true);
-
- // Re-get it from alpha, make sure its removed there too
- let getCommunityUrl = `${lemmyAlphaApiUrl}/community?id=${communityOnAlphaId}&auth=${lemmyAlphaAuth}`;
- let getCommunityRes: GetCommunityResponse = await fetch(getCommunityUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getCommunityRes.community.removed).toBe(true);
-
- // lemmy_beta unremoves the community
- let unremoveCommunityForm: RemoveCommunityForm = {
- edit_id: createCommunityRes.community.id,
- removed: false,
- auth: lemmyBetaAuth,
- };
-
- let unremoveCommunityRes: CommunityResponse = await fetch(
- `${lemmyBetaApiUrl}/community/remove`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unremoveCommunityForm),
- }
- ).then(d => d.json());
-
- // Make sure the delete went through
- expect(unremoveCommunityRes.community.removed).toBe(false);
-
- // Re-get it from alpha, make sure its deleted there too
- let getCommunityResAgain: GetCommunityResponse = await fetch(
- getCommunityUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
- expect(getCommunityResAgain.community.removed).toBe(false);
- });
- });
-
- describe('private message', () => {
- test('/u/lemmy_alpha creates/updates/deletes/undeletes a private_message to /u/lemmy_beta, its on both instances', async () => {
- let content = 'A jest test federated private message';
- let privateMessageForm: PrivateMessageForm = {
- content,
- recipient_id: 3,
- auth: lemmyAlphaAuth,
- };
-
- let createRes: PrivateMessageResponse = await fetch(
- `${lemmyAlphaApiUrl}/private_message`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(privateMessageForm),
- }
- ).then(d => d.json());
- expect(createRes.message.content).toBe(content);
- expect(createRes.message.local).toBe(true);
- expect(createRes.message.creator_local).toBe(true);
- expect(createRes.message.recipient_local).toBe(false);
-
- // Get it from beta
- let getPrivateMessagesUrl = `${lemmyBetaApiUrl}/private_message/list?auth=${lemmyBetaAuth}&unread_only=false`;
-
- let getPrivateMessagesRes: PrivateMessagesResponse = await fetch(
- getPrivateMessagesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(getPrivateMessagesRes.messages[0].content).toBe(content);
- expect(getPrivateMessagesRes.messages[0].local).toBe(false);
- expect(getPrivateMessagesRes.messages[0].creator_local).toBe(false);
- expect(getPrivateMessagesRes.messages[0].recipient_local).toBe(true);
-
- // lemmy alpha updates the private message
- let updatedContent = 'A jest test federated private message edited';
- let updatePrivateMessageForm: EditPrivateMessageForm = {
- content: updatedContent,
- edit_id: createRes.message.id,
- auth: lemmyAlphaAuth,
- };
-
- let updateRes: PrivateMessageResponse = await fetch(
- `${lemmyAlphaApiUrl}/private_message`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(updatePrivateMessageForm),
- }
- ).then(d => d.json());
-
- expect(updateRes.message.content).toBe(updatedContent);
-
- // Fetch from beta again
- let getPrivateMessagesUpdatedRes: PrivateMessagesResponse = await fetch(
- getPrivateMessagesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(getPrivateMessagesUpdatedRes.messages[0].content).toBe(
- updatedContent
- );
-
- // lemmy alpha deletes the private message
- let deletePrivateMessageForm: DeletePrivateMessageForm = {
- deleted: true,
- edit_id: createRes.message.id,
- auth: lemmyAlphaAuth,
- };
-
- let deleteRes: PrivateMessageResponse = await fetch(
- `${lemmyAlphaApiUrl}/private_message/delete`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(deletePrivateMessageForm),
- }
- ).then(d => d.json());
-
- expect(deleteRes.message.deleted).toBe(true);
-
- // Fetch from beta again
- let getPrivateMessagesDeletedRes: PrivateMessagesResponse = await fetch(
- getPrivateMessagesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- // The GetPrivateMessages filters out deleted,
- // even though they are in the actual database.
- // no reason to show them
- expect(getPrivateMessagesDeletedRes.messages.length).toBe(0);
-
- // lemmy alpha undeletes the private message
- let undeletePrivateMessageForm: DeletePrivateMessageForm = {
- deleted: false,
- edit_id: createRes.message.id,
- auth: lemmyAlphaAuth,
- };
-
- let undeleteRes: PrivateMessageResponse = await fetch(
- `${lemmyAlphaApiUrl}/private_message/delete`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(undeletePrivateMessageForm),
- }
- ).then(d => d.json());
-
- expect(undeleteRes.message.deleted).toBe(false);
-
- // Fetch from beta again
- let getPrivateMessagesUnDeletedRes: PrivateMessagesResponse = await fetch(
- getPrivateMessagesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
-
- expect(getPrivateMessagesUnDeletedRes.messages[0].deleted).toBe(false);
- });
- });
-
- describe('comment_search', () => {
- test('Create comment on alpha and search it', async () => {
- let content = 'A jest test federated comment for search';
- let commentForm: CommentForm = {
- content,
- post_id: 1,
- auth: lemmyAlphaAuth,
- };
-
- let createResponse: CommentResponse = await fetch(
- `${lemmyAlphaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(commentForm),
- }
- ).then(d => d.json());
-
- let searchUrl = `${lemmyBetaApiUrl}/search?q=${createResponse.comment.ap_id}&type_=All&sort=TopAll`;
- let searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- // TODO: check more fields
- expect(searchResponse.comments[0].content).toBe(content);
- });
- });
-
- describe('announce', () => {
- test('A and G subscribe to B (center) A does action, it gets announced to G', async () => {
- // A and G are already subscribed to B earlier.
- //
- let postName = 'A jest test post for announce';
- let createPostForm: PostForm = {
- name: postName,
- auth: lemmyAlphaAuth,
- community_id: 2,
- nsfw: false,
- };
-
- let createPostRes: PostResponse = await fetch(
- `${lemmyAlphaApiUrl}/post`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createPostForm),
- }
- ).then(d => d.json());
- expect(createPostRes.post.name).toBe(postName);
-
- // Make sure that post got announced to Gamma
- let searchUrl = `${lemmyGammaApiUrl}/search?q=${createPostRes.post.ap_id}&type_=All&sort=TopAll`;
- let searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
- let postId = searchResponse.posts[0].id;
- expect(searchResponse.posts[0].name).toBe(postName);
-
- // 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';
-
- let commentForm: CommentForm = {
- content: commentContent,
- post_id: postId,
- auth: lemmyGammaAuth,
- };
-
- let createCommentRes: CommentResponse = await fetch(
- `${lemmyGammaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(commentForm),
- }
- ).then(d => d.json());
-
- expect(createCommentRes.comment.content).toBe(commentContent);
- expect(createCommentRes.comment.community_local).toBe(false);
- expect(createCommentRes.comment.creator_local).toBe(true);
- expect(createCommentRes.comment.score).toBe(1);
-
- // Get the post from alpha, make sure it has gamma's comment
- let getPostUrl = `${lemmyAlphaApiUrl}/post?id=5`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.comments[0].content).toBe(commentContent);
- expect(getPostRes.comments[0].community_local).toBe(true);
- expect(getPostRes.comments[0].creator_local).toBe(false);
- expect(getPostRes.comments[0].score).toBe(1);
- });
- });
-
- describe('fetch inreplytos', () => {
- test('A is unsubbed from B, B makes a post, and some embedded comments, A subs to B, B updates the lowest level comment, A fetches both the post and all the inreplyto comments for that post.', async () => {
- // Check that A is subscribed to B
- let followedCommunitiesUrl = `${lemmyAlphaApiUrl}/user/followed_communities?&auth=${lemmyAlphaAuth}`;
- let followedCommunitiesRes: GetFollowedCommunitiesResponse = await fetch(
- followedCommunitiesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
- expect(followedCommunitiesRes.communities[1].community_local).toBe(false);
-
- // A unsubs from B (communities ids 3-5)
- for (let i = 3; i <= 5; i++) {
- let unfollowForm: FollowCommunityForm = {
- community_id: i,
- follow: false,
- auth: lemmyAlphaAuth,
- };
-
- let unfollowRes: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(unfollowForm),
- }
- ).then(d => d.json());
- expect(unfollowRes.community.local).toBe(false);
- }
-
- // Check that you are unsubscribed from all of them locally
- let followedCommunitiesResAgain: GetFollowedCommunitiesResponse = await fetch(
- followedCommunitiesUrl,
- {
- method: 'GET',
- }
- ).then(d => d.json());
- expect(followedCommunitiesResAgain.communities.length).toBe(1);
-
- // B creates a post, and two comments, should be invisible to A
- let betaPostName = 'Test post on B, invisible to A at first';
- let postForm: PostForm = {
- name: betaPostName,
- auth: lemmyBetaAuth,
- community_id: 2,
- nsfw: false,
- };
-
- let createPostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(postForm),
- }).then(d => d.json());
- expect(createPostRes.post.name).toBe(betaPostName);
-
- // B creates a comment, then a child one of that.
- let parentCommentContent = 'An invisible top level comment from beta';
- let createParentCommentForm: CommentForm = {
- content: parentCommentContent,
- post_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- };
-
- let createParentCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createParentCommentForm),
- }
- ).then(d => d.json());
- expect(createParentCommentRes.comment.content).toBe(parentCommentContent);
-
- let childCommentContent = 'An invisible child comment from beta';
- let createChildCommentForm: CommentForm = {
- content: childCommentContent,
- parent_id: createParentCommentRes.comment.id,
- post_id: createPostRes.post.id,
- auth: lemmyBetaAuth,
- };
-
- let createChildCommentRes: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(createChildCommentForm),
- }
- ).then(d => d.json());
- 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 searchResponse: SearchResponse = await fetch(searchUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(searchResponse.communities[0].name).toBe('main');
-
- let followForm: FollowCommunityForm = {
- community_id: searchResponse.communities[0].id,
- follow: true,
- auth: lemmyAlphaAuth,
- };
-
- let followResAgain: CommunityResponse = await fetch(
- `${lemmyAlphaApiUrl}/community/follow`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(followForm),
- }
- ).then(d => d.json());
-
- // Make sure the follow response went through
- expect(followResAgain.community.local).toBe(false);
- expect(followResAgain.community.name).toBe('main');
-
- let updatedCommentContent = 'An update child comment from beta';
- let updatedCommentForm: CommentForm = {
- content: updatedCommentContent,
- post_id: createPostRes.post.id,
- edit_id: createChildCommentRes.comment.id,
- auth: lemmyBetaAuth,
- creator_id: 2,
- };
-
- let updateResponse: CommentResponse = await fetch(
- `${lemmyBetaApiUrl}/comment`,
- {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: wrapper(updatedCommentForm),
- }
- ).then(d => d.json());
- expect(updateResponse.comment.content).toBe(updatedCommentContent);
-
- // Make sure that A picked up the post, parent comment, and child comment
- let getPostUrl = `${lemmyAlphaApiUrl}/post?id=6`;
- let getPostRes: GetPostResponse = await fetch(getPostUrl, {
- method: 'GET',
- }).then(d => d.json());
-
- expect(getPostRes.post.name).toBe(betaPostName);
- expect(getPostRes.comments[1].content).toBe(parentCommentContent);
- expect(getPostRes.comments[0].content).toBe(updatedCommentContent);
- expect(getPostRes.post.community_local).toBe(false);
- expect(getPostRes.post.creator_local).toBe(false);
- });
- });
-});
-
-function wrapper(form: any): string {
- return JSON.stringify(form);
-}
--- /dev/null
+import {
+ alpha,
+ beta,
+ gamma,
+ setupLogins,
+ createPost,
+ getPost,
+ searchComment,
+ likeComment,
+ followBeta,
+ searchForBetaCommunity,
+ createComment,
+ updateComment,
+ deleteComment,
+ removeComment,
+ getMentions,
+ searchPost,
+ unfollowRemotes,
+} from './shared';
+
+import { PostResponse } from '../interfaces';
+
+let postRes: PostResponse;
+
+beforeAll(async () => {
+ await setupLogins();
+ await followBeta(alpha);
+ await followBeta(gamma);
+ let search = await searchForBetaCommunity(alpha);
+ postRes = await createPost(
+ alpha,
+ search.communities.filter(c => c.local == false)[0].id
+ );
+});
+
+afterAll(async () => {
+ await unfollowRemotes(alpha);
+ await unfollowRemotes(gamma);
+});
+
+test('Create a comment', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+ expect(commentRes.comment.content).toBeDefined();
+ expect(commentRes.comment.community_local).toBe(false);
+ expect(commentRes.comment.creator_local).toBe(true);
+ expect(commentRes.comment.score).toBe(1);
+
+ // Make sure that comment is liked on beta
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+ expect(betaComment).toBeDefined();
+ expect(betaComment.community_local).toBe(true);
+ expect(betaComment.creator_local).toBe(false);
+ expect(betaComment.score).toBe(1);
+});
+
+test('Update a comment', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let updateCommentRes = await updateComment(alpha, commentRes.comment.id);
+ expect(updateCommentRes.comment.content).toBe(
+ 'A jest test federated comment update'
+ );
+ expect(updateCommentRes.comment.community_local).toBe(false);
+ expect(updateCommentRes.comment.creator_local).toBe(true);
+
+ // Make sure that post is updated on beta
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+ expect(betaComment.content).toBe('A jest test federated comment update');
+});
+
+test('Delete a comment', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let deleteCommentRes = await deleteComment(
+ alpha,
+ true,
+ commentRes.comment.id
+ );
+ expect(deleteCommentRes.comment.deleted).toBe(true);
+
+ // Make sure that comment is deleted on beta
+ // The search doesnt work below, because it returns a tombstone / http::gone
+ // let searchBeta = await searchComment(beta, commentRes.comment);
+ // console.log(searchBeta);
+ // let betaComment = searchBeta.comments[0];
+ // Create a fake post, just to get the previous new post id
+ let createdBetaPostJustToGetId = await createPost(beta, 2);
+ let betaPost = await getPost(beta, createdBetaPostJustToGetId.post.id - 1);
+ let betaComment = betaPost.comments[0];
+ expect(betaComment.deleted).toBe(true);
+
+ let undeleteCommentRes = await deleteComment(
+ alpha,
+ false,
+ commentRes.comment.id
+ );
+ expect(undeleteCommentRes.comment.deleted).toBe(false);
+
+ // Make sure that comment is undeleted on beta
+ let searchBeta2 = await searchComment(beta, commentRes.comment);
+ let betaComment2 = searchBeta2.comments[0];
+ expect(betaComment2.deleted).toBe(false);
+});
+
+test('Remove a comment', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let removeCommentRes = await removeComment(
+ alpha,
+ true,
+ commentRes.comment.id
+ );
+ expect(removeCommentRes.comment.removed).toBe(true);
+
+ // Make sure that comment is removed on beta
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+ expect(betaComment.removed).toBe(true);
+
+ let unremoveCommentRes = await removeComment(
+ alpha,
+ false,
+ commentRes.comment.id
+ );
+ expect(unremoveCommentRes.comment.removed).toBe(false);
+
+ // Make sure that comment is unremoved on beta
+ let searchBeta2 = await searchComment(beta, commentRes.comment);
+ let betaComment2 = searchBeta2.comments[0];
+ expect(betaComment2.removed).toBe(false);
+});
+
+test('Unlike a comment', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let unlike = await likeComment(alpha, 0, commentRes.comment);
+ expect(unlike.comment.score).toBe(0);
+
+ // Make sure that post is unliked on beta
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+ expect(betaComment).toBeDefined();
+ expect(betaComment.community_local).toBe(true);
+ expect(betaComment.creator_local).toBe(false);
+ expect(betaComment.score).toBe(0);
+});
+
+test('Federated comment like', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+
+ // Find the comment on beta
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+
+ let like = await likeComment(beta, 1, betaComment);
+ expect(like.comment.score).toBe(2);
+
+ // Get the post from alpha, check the likes
+ let post = await getPost(alpha, postRes.post.id);
+ expect(post.comments[0].score).toBe(2);
+});
+
+test('Reply to a comment', async () => {
+ // Create a comment on alpha, find it on beta
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ let betaComment = searchBeta.comments[0];
+
+ // find that comment id on beta
+
+ // Reply from beta
+ let replyRes = await createComment(beta, betaComment.post_id, betaComment.id);
+ expect(replyRes.comment.content).toBeDefined();
+ expect(replyRes.comment.community_local).toBe(true);
+ expect(replyRes.comment.creator_local).toBe(true);
+ expect(replyRes.comment.parent_id).toBe(betaComment.id);
+ expect(replyRes.comment.score).toBe(1);
+
+ // Make sure that comment is seen on alpha
+ // TODO not sure why, but a searchComment back to alpha, for the ap_id of betas
+ // comment, isn't working.
+ // let searchAlpha = await searchComment(alpha, replyRes.comment);
+ let post = await getPost(alpha, postRes.post.id);
+ let alphaComment = post.comments[0];
+ expect(alphaComment.content).toBeDefined();
+ expect(alphaComment.parent_id).toBe(post.comments[1].id);
+ expect(alphaComment.community_local).toBe(false);
+ expect(alphaComment.creator_local).toBe(false);
+ expect(alphaComment.score).toBe(1);
+});
+
+test('Mention beta', async () => {
+ // Create a mention on alpha
+ let mentionContent = 'A test mention of @lemmy_beta@lemmy-beta:8550';
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let mentionRes = await createComment(
+ alpha,
+ postRes.post.id,
+ commentRes.comment.id,
+ mentionContent
+ );
+ expect(mentionRes.comment.content).toBeDefined();
+ expect(mentionRes.comment.community_local).toBe(false);
+ expect(mentionRes.comment.creator_local).toBe(true);
+ expect(mentionRes.comment.score).toBe(1);
+
+ let mentionsRes = await getMentions(beta);
+ expect(mentionsRes.mentions[0].content).toBeDefined();
+ expect(mentionsRes.mentions[0].community_local).toBe(true);
+ expect(mentionsRes.mentions[0].creator_local).toBe(false);
+ expect(mentionsRes.mentions[0].score).toBe(1);
+});
+
+test('Comment Search', async () => {
+ let commentRes = await createComment(alpha, postRes.post.id);
+ let searchBeta = await searchComment(beta, commentRes.comment);
+ expect(searchBeta.comments[0].ap_id).toBe(commentRes.comment.ap_id);
+});
+
+test('A and G subscribe to B (center) A posts, G mentions B, it gets announced to A', async () => {
+ // Create a local post
+ let alphaPost = await createPost(alpha, 2);
+ expect(alphaPost.post.community_local).toBe(true);
+
+ // Make sure gamma sees it
+ let search = await searchPost(gamma, alphaPost.post);
+ let gammaPost = search.posts[0];
+
+ let commentContent =
+ 'A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8550';
+ let commentRes = await createComment(
+ gamma,
+ gammaPost.id,
+ undefined,
+ commentContent
+ );
+ expect(commentRes.comment.content).toBe(commentContent);
+ expect(commentRes.comment.community_local).toBe(false);
+ expect(commentRes.comment.creator_local).toBe(true);
+ expect(commentRes.comment.score).toBe(1);
+
+ // Make sure alpha sees it
+ let alphaPost2 = await getPost(alpha, alphaPost.post.id);
+ expect(alphaPost2.comments[0].content).toBe(commentContent);
+ expect(alphaPost2.comments[0].community_local).toBe(true);
+ expect(alphaPost2.comments[0].creator_local).toBe(false);
+ expect(alphaPost2.comments[0].score).toBe(1);
+
+ // Make sure beta has mentions
+ let mentionsRes = await getMentions(beta);
+ expect(mentionsRes.mentions[0].content).toBe(commentContent);
+ expect(mentionsRes.mentions[0].community_local).toBe(false);
+ expect(mentionsRes.mentions[0].creator_local).toBe(false);
+ // TODO this is failing because fetchInReplyTos aren't getting score
+ // expect(mentionsRes.mentions[0].score).toBe(1);
+});
+
+test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedded comments, A subs to B, B updates the lowest level comment, A fetches both the post and all the inreplyto comments for that post.', async () => {
+ // Unfollow all remote communities
+ let followed = await unfollowRemotes(alpha);
+ expect(
+ followed.communities.filter(c => c.community_local == false).length
+ ).toBe(0);
+
+ // B creates a post, and two comments, should be invisible to A
+ let postRes = await createPost(beta, 2);
+ expect(postRes.post.name).toBeDefined();
+
+ let parentCommentContent = 'An invisible top level comment from beta';
+ let parentCommentRes = await createComment(
+ beta,
+ postRes.post.id,
+ undefined,
+ parentCommentContent
+ );
+ expect(parentCommentRes.comment.content).toBe(parentCommentContent);
+
+ // B creates a comment, then a child one of that.
+ let childCommentContent = 'An invisible child comment from beta';
+ let childCommentRes = await createComment(
+ beta,
+ postRes.post.id,
+ parentCommentRes.comment.id,
+ childCommentContent
+ );
+ expect(childCommentRes.comment.content).toBe(childCommentContent);
+
+ // Follow beta again
+ let follow = await followBeta(alpha);
+ expect(follow.community.local).toBe(false);
+ expect(follow.community.name).toBe('main');
+
+ // An update to the child comment on beta, should push the post, parent, and child to alpha now
+ let updatedCommentContent = 'An update child comment from beta';
+ let updateRes = await updateComment(
+ beta,
+ childCommentRes.comment.id,
+ updatedCommentContent
+ );
+ expect(updateRes.comment.content).toBe(updatedCommentContent);
+
+ // Get the post from alpha
+ let createFakeAlphaPostToGetId = await createPost(alpha, 2);
+ let alphaPost = await getPost(alpha, createFakeAlphaPostToGetId.post.id - 1);
+ expect(alphaPost.post.name).toBeDefined();
+ expect(alphaPost.comments[1].content).toBe(parentCommentContent);
+ expect(alphaPost.comments[0].content).toBe(updatedCommentContent);
+ expect(alphaPost.post.community_local).toBe(false);
+ expect(alphaPost.post.creator_local).toBe(false);
+});
--- /dev/null
+import {
+ alpha,
+ beta,
+ setupLogins,
+ searchForBetaCommunity,
+ createCommunity,
+ deleteCommunity,
+ removeCommunity,
+} from './shared';
+
+beforeAll(async () => {
+ await setupLogins();
+});
+
+test('Create community', async () => {
+ let communityRes = await createCommunity(alpha);
+ expect(communityRes.community.name).toBeDefined();
+
+ // A dupe check
+ let prevName = communityRes.community.name;
+ let communityRes2 = await createCommunity(alpha, prevName);
+ expect(communityRes2['error']).toBe('community_already_exists');
+});
+
+test('Delete community', async () => {
+ let communityRes = await createCommunity(beta);
+ let deleteCommunityRes = await deleteCommunity(
+ beta,
+ true,
+ communityRes.community.id
+ );
+ expect(deleteCommunityRes.community.deleted).toBe(true);
+
+ // Make sure it got deleted on A
+ let search = await searchForBetaCommunity(alpha);
+ let communityA = search.communities[0];
+ // TODO this fails currently, because no updates are pushed
+ // expect(communityA.deleted).toBe(true);
+
+ // Undelete
+ let undeleteCommunityRes = await deleteCommunity(
+ beta,
+ false,
+ communityRes.community.id
+ );
+ expect(undeleteCommunityRes.community.deleted).toBe(false);
+
+ // Make sure it got undeleted on A
+ let search2 = await searchForBetaCommunity(alpha);
+ let communityA2 = search2.communities[0];
+ // TODO this fails currently, because no updates are pushed
+ // expect(communityA2.deleted).toBe(false);
+});
+
+test('Remove community', async () => {
+ let communityRes = await createCommunity(beta);
+ let removeCommunityRes = await removeCommunity(
+ beta,
+ true,
+ communityRes.community.id
+ );
+ expect(removeCommunityRes.community.removed).toBe(true);
+
+ // Make sure it got removed on A
+ let search = await searchForBetaCommunity(alpha);
+ let communityA = search.communities[0];
+ // TODO this fails currently, because no updates are pushed
+ // expect(communityA.removed).toBe(true);
+
+ // unremove
+ let unremoveCommunityRes = await removeCommunity(
+ beta,
+ false,
+ communityRes.community.id
+ );
+ expect(unremoveCommunityRes.community.removed).toBe(false);
+
+ // Make sure it got unremoved on A
+ let search2 = await searchForBetaCommunity(alpha);
+ let communityA2 = search2.communities[0];
+ // TODO this fails currently, because no updates are pushed
+ // expect(communityA2.removed).toBe(false);
+});
+
+test('Search for beta community', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ expect(search.communities[0].name).toBe('main');
+});
--- /dev/null
+import {
+ alpha,
+ setupLogins,
+ searchForBetaCommunity,
+ followCommunity,
+ checkFollowedCommunities,
+ unfollowRemotes,
+} from './shared';
+
+beforeAll(async () => {
+ await setupLogins();
+});
+
+afterAll(async () => {
+ await unfollowRemotes(alpha);
+});
+
+test('Follow federated community', async () => {
+ let search = await searchForBetaCommunity(alpha); // TODO sometimes this is returning null?
+ let follow = await followCommunity(alpha, true, search.communities[0].id);
+
+ // Make sure the follow response went through
+ expect(follow.community.local).toBe(false);
+ expect(follow.community.name).toBe('main');
+
+ // Check it from local
+ let followCheck = await checkFollowedCommunities(alpha);
+ let remoteCommunityId = followCheck.communities.filter(
+ c => c.community_local == false
+ )[0].community_id;
+ expect(remoteCommunityId).toBeDefined();
+
+ // Test an unfollow
+ let unfollow = await followCommunity(alpha, false, remoteCommunityId);
+ expect(unfollow.community.local).toBe(false);
+
+ // Make sure you are unsubbed locally
+ let unfollowCheck = await checkFollowedCommunities(alpha);
+ expect(unfollowCheck.communities.length).toBeGreaterThanOrEqual(1);
+});
--- /dev/null
+import {
+ alpha,
+ beta,
+ gamma,
+ setupLogins,
+ createPost,
+ updatePost,
+ stickyPost,
+ lockPost,
+ searchPost,
+ likePost,
+ followBeta,
+ searchForBetaCommunity,
+ createComment,
+ deletePost,
+ removePost,
+ getPost,
+ unfollowRemotes,
+} from './shared';
+
+beforeAll(async () => {
+ await setupLogins();
+ await followBeta(alpha);
+ await followBeta(gamma);
+});
+
+afterAll(async () => {
+ await unfollowRemotes(alpha);
+ await unfollowRemotes(gamma);
+});
+
+test('Create a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+ expect(postRes.post).toBeDefined();
+ expect(postRes.post.community_local).toBe(false);
+ expect(postRes.post.creator_local).toBe(true);
+ expect(postRes.post.score).toBe(1);
+
+ // Make sure that post is liked on beta
+ let searchBeta = await searchPost(beta, postRes.post);
+ let betaPost = searchBeta.posts[0];
+
+ expect(betaPost).toBeDefined();
+ expect(betaPost.community_local).toBe(true);
+ expect(betaPost.creator_local).toBe(false);
+ expect(betaPost.score).toBe(1);
+});
+
+test('Unlike a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+ let unlike = await likePost(alpha, 0, postRes.post);
+ expect(unlike.post.score).toBe(0);
+
+ // Make sure that post is unliked on beta
+ let searchBeta = await searchPost(beta, postRes.post);
+ let betaPost = searchBeta.posts[0];
+
+ expect(betaPost).toBeDefined();
+ expect(betaPost.community_local).toBe(true);
+ expect(betaPost.creator_local).toBe(false);
+ expect(betaPost.score).toBe(0);
+});
+
+test('Update a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let updatedPost = await updatePost(alpha, postRes.post);
+ expect(updatedPost.post.name).toBe('A jest test federated post, updated');
+ expect(updatedPost.post.community_local).toBe(false);
+ expect(updatedPost.post.creator_local).toBe(true);
+});
+
+test('Sticky a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let stickiedPostRes = await stickyPost(alpha, true, postRes.post);
+ expect(stickiedPostRes.post.stickied).toBe(true);
+
+ // Make sure that post is stickied on beta
+ let searchBeta = await searchPost(beta, postRes.post);
+ let betaPost = searchBeta.posts[0];
+ expect(betaPost.community_local).toBe(true);
+ expect(betaPost.creator_local).toBe(false);
+ expect(betaPost.stickied).toBe(true);
+
+ // Unsticky a post
+ let unstickiedPost = await stickyPost(alpha, false, postRes.post);
+ expect(unstickiedPost.post.stickied).toBe(false);
+
+ // Make sure that post is unstickied on beta
+ let searchBeta2 = await searchPost(beta, postRes.post);
+ let betaPost2 = searchBeta2.posts[0];
+ expect(betaPost2.community_local).toBe(true);
+ expect(betaPost2.creator_local).toBe(false);
+ expect(betaPost2.stickied).toBe(false);
+});
+
+test('Lock a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let lockedPostRes = await lockPost(alpha, true, postRes.post);
+ expect(lockedPostRes.post.locked).toBe(true);
+
+ // Make sure that post is locked on beta
+ let searchBeta = await searchPost(beta, postRes.post);
+ let betaPost = searchBeta.posts[0];
+ expect(betaPost.community_local).toBe(true);
+ expect(betaPost.creator_local).toBe(false);
+ expect(betaPost.locked).toBe(true);
+
+ // Try to make a new comment there, on alpha
+ let comment = await createComment(alpha, postRes.post.id);
+ expect(comment['error']).toBe('locked');
+
+ // Try to create a new comment, on beta
+ let commentBeta = await createComment(beta, betaPost.id);
+ expect(commentBeta['error']).toBe('locked');
+
+ // Unlock a post
+ let unlockedPost = await lockPost(alpha, false, postRes.post);
+ expect(unlockedPost.post.locked).toBe(false);
+
+ // Make sure that post is unlocked on beta
+ let searchBeta2 = await searchPost(beta, postRes.post);
+ let betaPost2 = searchBeta2.posts[0];
+ expect(betaPost2.community_local).toBe(true);
+ expect(betaPost2.creator_local).toBe(false);
+ expect(betaPost2.locked).toBe(false);
+});
+
+test('Delete a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let deletedPost = await deletePost(alpha, true, postRes.post);
+ expect(deletedPost.post.deleted).toBe(true);
+
+ // Make sure lemmy beta sees post is deleted
+ let createFakeBetaPostToGetId = (await createPost(beta, 2)).post.id - 1;
+ let betaPost = await getPost(beta, createFakeBetaPostToGetId);
+ expect(betaPost.post.deleted).toBe(true);
+
+ // Undelete
+ let undeletedPost = await deletePost(alpha, false, postRes.post);
+ expect(undeletedPost.post.deleted).toBe(false);
+
+ // Make sure lemmy beta sees post is undeleted
+ let betaPost2 = await getPost(beta, createFakeBetaPostToGetId);
+ expect(betaPost2.post.deleted).toBe(false);
+});
+
+test('Remove a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let removedPost = await removePost(alpha, true, postRes.post);
+ expect(removedPost.post.removed).toBe(true);
+
+ // Make sure lemmy beta sees post is removed
+ let createFakeBetaPostToGetId = (await createPost(beta, 2)).post.id - 1;
+ let betaPost = await getPost(beta, createFakeBetaPostToGetId);
+ expect(betaPost.post.removed).toBe(true);
+
+ // Undelete
+ let undeletedPost = await removePost(alpha, false, postRes.post);
+ expect(undeletedPost.post.removed).toBe(false);
+
+ // Make sure lemmy beta sees post is undeleted
+ let betaPost2 = await getPost(beta, createFakeBetaPostToGetId);
+ expect(betaPost2.post.removed).toBe(false);
+});
+
+test('Search for a post', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+ let searchBeta = await searchPost(beta, postRes.post);
+
+ expect(searchBeta.posts[0].name).toBeDefined();
+});
+
+test('A and G subscribe to B (center) A posts, it gets announced to G', async () => {
+ let search = await searchForBetaCommunity(alpha);
+ let postRes = await createPost(alpha, search.communities[0].id);
+
+ let search2 = await searchPost(gamma, postRes.post);
+ expect(search2.posts[0].name).toBeDefined();
+});
--- /dev/null
+import {
+ alpha,
+ beta,
+ setupLogins,
+ followBeta,
+ createPrivateMessage,
+ updatePrivateMessage,
+ listPrivateMessages,
+ deletePrivateMessage,
+ unfollowRemotes,
+} from './shared';
+
+let recipient_id: number;
+
+beforeAll(async () => {
+ await setupLogins();
+ recipient_id = (await followBeta(alpha)).community.creator_id;
+});
+
+afterAll(async () => {
+ await unfollowRemotes(alpha);
+});
+
+test('Create a private message', async () => {
+ let pmRes = await createPrivateMessage(alpha, recipient_id);
+ expect(pmRes.message.content).toBeDefined();
+ expect(pmRes.message.local).toBe(true);
+ expect(pmRes.message.creator_local).toBe(true);
+ expect(pmRes.message.recipient_local).toBe(false);
+
+ let betaPms = await listPrivateMessages(beta);
+ expect(betaPms.messages[0].content).toBeDefined();
+ expect(betaPms.messages[0].local).toBe(false);
+ expect(betaPms.messages[0].creator_local).toBe(false);
+ expect(betaPms.messages[0].recipient_local).toBe(true);
+});
+
+test('Update a private message', async () => {
+ let updatedContent = 'A jest test federated private message edited';
+
+ let pmRes = await createPrivateMessage(alpha, recipient_id);
+ let pmUpdated = await updatePrivateMessage(alpha, pmRes.message.id);
+ expect(pmUpdated.message.content).toBe(updatedContent);
+
+ let betaPms = await listPrivateMessages(beta);
+ expect(betaPms.messages[0].content).toBe(updatedContent);
+});
+
+test('Delete a private message', async () => {
+ let pmRes = await createPrivateMessage(alpha, recipient_id);
+ let betaPms1 = await listPrivateMessages(beta);
+ let deletedPmRes = await deletePrivateMessage(alpha, true, pmRes.message.id);
+ expect(deletedPmRes.message.deleted).toBe(true);
+
+ // The GetPrivateMessages filters out deleted,
+ // even though they are in the actual database.
+ // no reason to show them
+ let betaPms2 = await listPrivateMessages(beta);
+ expect(betaPms2.messages.length).toBe(betaPms1.messages.length - 1);
+
+ // Undelete
+ let undeletedPmRes = await deletePrivateMessage(
+ alpha,
+ false,
+ pmRes.message.id
+ );
+ expect(undeletedPmRes.message.deleted).toBe(false);
+
+ let betaPms3 = await listPrivateMessages(beta);
+ expect(betaPms3.messages.length).toBe(betaPms1.messages.length);
+});
--- /dev/null
+import fetch from 'node-fetch';
+
+import {
+ LoginForm,
+ LoginResponse,
+ Post,
+ PostForm,
+ Comment,
+ DeletePostForm,
+ RemovePostForm,
+ StickyPostForm,
+ LockPostForm,
+ PostResponse,
+ SearchResponse,
+ FollowCommunityForm,
+ CommunityResponse,
+ GetFollowedCommunitiesResponse,
+ GetPostResponse,
+ CommentForm,
+ DeleteCommentForm,
+ RemoveCommentForm,
+ CommentResponse,
+ CommunityForm,
+ DeleteCommunityForm,
+ RemoveCommunityForm,
+ CommentLikeForm,
+ CreatePostLikeForm,
+ PrivateMessageForm,
+ EditPrivateMessageForm,
+ DeletePrivateMessageForm,
+ PrivateMessageResponse,
+ PrivateMessagesResponse,
+ GetUserMentionsResponse,
+} from '../interfaces';
+
+export interface API {
+ url: string;
+ auth?: string;
+}
+
+function apiUrl(api: API) {
+ return `${api.url}/api/v1`;
+}
+
+export let alpha: API = {
+ url: 'http://localhost:8540',
+};
+
+export let beta: API = {
+ url: 'http://localhost:8550',
+};
+
+export let gamma: API = {
+ url: 'http://localhost:8560',
+};
+
+export async function setupLogins() {
+ let form: LoginForm = {
+ username_or_email: 'lemmy_alpha',
+ password: 'lemmy',
+ };
+
+ let resA: Promise<LoginResponse> = fetch(`${apiUrl(alpha)}/user/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(form),
+ }).then(d => d.json());
+
+ let formB = {
+ username_or_email: 'lemmy_beta',
+ password: 'lemmy',
+ };
+
+ let resB: Promise<LoginResponse> = fetch(`${apiUrl(beta)}/user/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(formB),
+ }).then(d => d.json());
+
+ let formC = {
+ username_or_email: 'lemmy_gamma',
+ password: 'lemmy',
+ };
+
+ let resG: Promise<LoginResponse> = fetch(`${apiUrl(gamma)}/user/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(formC),
+ }).then(d => d.json());
+
+ let res = await Promise.all([resA, resB, resG]);
+ alpha.auth = res[0].jwt;
+ beta.auth = res[1].jwt;
+ gamma.auth = res[2].jwt;
+}
+
+export async function createPost(
+ api: API,
+ community_id: number
+): Promise<PostResponse> {
+ let name = 'A jest test post';
+ let postForm: PostForm = {
+ name,
+ auth: api.auth,
+ community_id,
+ nsfw: false,
+ };
+
+ let createPostRes: PostResponse = await fetch(`${apiUrl(api)}/post`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(postForm),
+ }).then(d => d.json());
+ return createPostRes;
+}
+
+export async function updatePost(api: API, post: Post): Promise<PostResponse> {
+ let name = 'A jest test federated post, updated';
+ let postForm: PostForm = {
+ name,
+ edit_id: post.id,
+ auth: api.auth,
+ nsfw: false,
+ };
+
+ let updateResponse: PostResponse = await fetch(`${apiUrl(api)}/post`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(postForm),
+ }).then(d => d.json());
+ return updateResponse;
+}
+
+export async function deletePost(
+ api: API,
+ deleted: boolean,
+ post: Post
+): Promise<PostResponse> {
+ let deletePostForm: DeletePostForm = {
+ edit_id: post.id,
+ deleted: deleted,
+ auth: api.auth,
+ };
+
+ let deletePostRes: PostResponse = await fetch(`${apiUrl(api)}/post/delete`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(deletePostForm),
+ }).then(d => d.json());
+ return deletePostRes;
+}
+
+export async function removePost(
+ api: API,
+ removed: boolean,
+ post: Post
+): Promise<PostResponse> {
+ let removePostForm: RemovePostForm = {
+ edit_id: post.id,
+ removed,
+ auth: api.auth,
+ };
+
+ let removePostRes: PostResponse = await fetch(`${apiUrl(api)}/post/remove`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(removePostForm),
+ }).then(d => d.json());
+ return removePostRes;
+}
+
+export async function stickyPost(
+ api: API,
+ stickied: boolean,
+ post: Post
+): Promise<PostResponse> {
+ let stickyPostForm: StickyPostForm = {
+ edit_id: post.id,
+ stickied,
+ auth: api.auth,
+ };
+
+ let stickyRes: PostResponse = await fetch(`${apiUrl(api)}/post/sticky`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(stickyPostForm),
+ }).then(d => d.json());
+
+ return stickyRes;
+}
+
+export async function lockPost(
+ api: API,
+ locked: boolean,
+ post: Post
+): Promise<PostResponse> {
+ let lockPostForm: LockPostForm = {
+ edit_id: post.id,
+ locked,
+ auth: api.auth,
+ };
+
+ let lockRes: PostResponse = await fetch(`${apiUrl(api)}/post/lock`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(lockPostForm),
+ }).then(d => d.json());
+
+ return lockRes;
+}
+
+export async function searchPost(
+ api: API,
+ post: Post
+): Promise<SearchResponse> {
+ let searchUrl = `${apiUrl(api)}/search?q=${post.ap_id}&type_=All&sort=TopAll`;
+ let searchResponse: SearchResponse = await fetch(searchUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ return searchResponse;
+}
+
+export async function getPost(
+ api: API,
+ post_id: number
+): Promise<GetPostResponse> {
+ let getPostUrl = `${apiUrl(api)}/post?id=${post_id}`;
+ let getPostRes: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ return getPostRes;
+}
+
+export async function searchComment(
+ api: API,
+ comment: Comment
+): Promise<SearchResponse> {
+ let searchUrl = `${apiUrl(api)}/search?q=${
+ comment.ap_id
+ }&type_=All&sort=TopAll`;
+ let searchResponse: SearchResponse = await fetch(searchUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ return searchResponse;
+}
+
+export async function searchForBetaCommunity(
+ api: API
+): Promise<SearchResponse> {
+ // Make sure lemmy-beta/c/main is cached on lemmy_alpha
+ // Use short-hand search url
+ let searchUrl = `${apiUrl(
+ api
+ )}/search?q=!main@lemmy-beta:8550&type_=All&sort=TopAll`;
+
+ let searchResponse: SearchResponse = await fetch(searchUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ return searchResponse;
+}
+
+export async function followCommunity(
+ api: API,
+ follow: boolean,
+ community_id: number
+): Promise<CommunityResponse> {
+ let followForm: FollowCommunityForm = {
+ community_id,
+ follow,
+ auth: api.auth,
+ };
+
+ let followRes: CommunityResponse = await fetch(
+ `${apiUrl(api)}/community/follow`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(followForm),
+ }
+ )
+ .then(d => d.json())
+ .catch(_e => {});
+
+ return followRes;
+}
+
+export async function checkFollowedCommunities(
+ api: API
+): Promise<GetFollowedCommunitiesResponse> {
+ let followedCommunitiesUrl = `${apiUrl(
+ api
+ )}/user/followed_communities?&auth=${api.auth}`;
+ let followedCommunitiesRes: GetFollowedCommunitiesResponse = await fetch(
+ followedCommunitiesUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+ return followedCommunitiesRes;
+}
+
+export async function likePost(
+ api: API,
+ score: number,
+ post: Post
+): Promise<PostResponse> {
+ let likePostForm: CreatePostLikeForm = {
+ post_id: post.id,
+ score: score,
+ auth: api.auth,
+ };
+
+ let likePostRes: PostResponse = await fetch(`${apiUrl(api)}/post/like`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(likePostForm),
+ }).then(d => d.json());
+
+ return likePostRes;
+}
+
+export async function createComment(
+ api: API,
+ post_id: number,
+ parent_id?: number,
+ content = 'a jest test comment'
+): Promise<CommentResponse> {
+ let commentForm: CommentForm = {
+ content,
+ post_id,
+ parent_id,
+ auth: api.auth,
+ };
+
+ let createResponse: CommentResponse = await fetch(`${apiUrl(api)}/comment`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(commentForm),
+ }).then(d => d.json());
+ return createResponse;
+}
+
+export async function updateComment(
+ api: API,
+ edit_id: number,
+ content = 'A jest test federated comment update'
+): Promise<CommentResponse> {
+ let commentForm: CommentForm = {
+ content,
+ edit_id,
+ auth: api.auth,
+ };
+
+ let updateResponse: CommentResponse = await fetch(`${apiUrl(api)}/comment`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(commentForm),
+ }).then(d => d.json());
+ return updateResponse;
+}
+
+export async function deleteComment(
+ api: API,
+ deleted: boolean,
+ edit_id: number
+): Promise<CommentResponse> {
+ let deleteCommentForm: DeleteCommentForm = {
+ edit_id,
+ deleted,
+ auth: api.auth,
+ };
+
+ let deleteCommentRes: CommentResponse = await fetch(
+ `${apiUrl(api)}/comment/delete`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(deleteCommentForm),
+ }
+ ).then(d => d.json());
+ return deleteCommentRes;
+}
+
+export async function removeComment(
+ api: API,
+ removed: boolean,
+ edit_id: number
+): Promise<CommentResponse> {
+ let removeCommentForm: RemoveCommentForm = {
+ edit_id,
+ removed,
+ auth: api.auth,
+ };
+
+ let removeCommentRes: CommentResponse = await fetch(
+ `${apiUrl(api)}/comment/remove`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(removeCommentForm),
+ }
+ ).then(d => d.json());
+ return removeCommentRes;
+}
+
+export async function getMentions(api: API): Promise<GetUserMentionsResponse> {
+ let getMentionUrl = `${apiUrl(
+ api
+ )}/user/mention?sort=New&unread_only=false&auth=${api.auth}`;
+ let getMentionsRes: GetUserMentionsResponse = await fetch(getMentionUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ return getMentionsRes;
+}
+
+export async function likeComment(
+ api: API,
+ score: number,
+ comment: Comment
+): Promise<CommentResponse> {
+ let likeCommentForm: CommentLikeForm = {
+ comment_id: comment.id,
+ score,
+ auth: api.auth,
+ };
+
+ let likeCommentRes: CommentResponse = await fetch(
+ `${apiUrl(api)}/comment/like`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(likeCommentForm),
+ }
+ ).then(d => d.json());
+ return likeCommentRes;
+}
+
+export async function createCommunity(
+ api: API,
+ name_: string = randomString(5)
+): Promise<CommunityResponse> {
+ let communityForm: CommunityForm = {
+ name: name_,
+ title: name_,
+ category_id: 1,
+ nsfw: false,
+ auth: api.auth,
+ };
+
+ let createCommunityRes: CommunityResponse = await fetch(
+ `${apiUrl(api)}/community`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(communityForm),
+ }
+ ).then(d => d.json());
+ return createCommunityRes;
+}
+
+export async function deleteCommunity(
+ api: API,
+ deleted: boolean,
+ edit_id: number
+): Promise<CommunityResponse> {
+ let deleteCommunityForm: DeleteCommunityForm = {
+ edit_id,
+ deleted,
+ auth: api.auth,
+ };
+
+ let deleteResponse: CommunityResponse = await fetch(
+ `${apiUrl(api)}/community/delete`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(deleteCommunityForm),
+ }
+ ).then(d => d.json());
+ return deleteResponse;
+}
+
+export async function removeCommunity(
+ api: API,
+ removed: boolean,
+ edit_id: number
+): Promise<CommunityResponse> {
+ let removeCommunityForm: RemoveCommunityForm = {
+ edit_id,
+ removed,
+ auth: api.auth,
+ };
+
+ let removeResponse: CommunityResponse = await fetch(
+ `${apiUrl(api)}/community/remove`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(removeCommunityForm),
+ }
+ ).then(d => d.json());
+ return removeResponse;
+}
+
+export async function createPrivateMessage(
+ api: API,
+ recipient_id: number
+): Promise<PrivateMessageResponse> {
+ let content = 'A jest test federated private message';
+ let privateMessageForm: PrivateMessageForm = {
+ content,
+ recipient_id,
+ auth: api.auth,
+ };
+
+ let createRes: PrivateMessageResponse = await fetch(
+ `${apiUrl(api)}/private_message`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(privateMessageForm),
+ }
+ ).then(d => d.json());
+ return createRes;
+}
+
+export async function updatePrivateMessage(
+ api: API,
+ edit_id: number
+): Promise<PrivateMessageResponse> {
+ let updatedContent = 'A jest test federated private message edited';
+ let updatePrivateMessageForm: EditPrivateMessageForm = {
+ content: updatedContent,
+ edit_id,
+ auth: api.auth,
+ };
+
+ let updateRes: PrivateMessageResponse = await fetch(
+ `${apiUrl(api)}/private_message`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(updatePrivateMessageForm),
+ }
+ ).then(d => d.json());
+ return updateRes;
+}
+
+export async function deletePrivateMessage(
+ api: API,
+ deleted: boolean,
+ edit_id: number
+): Promise<PrivateMessageResponse> {
+ let deletePrivateMessageForm: DeletePrivateMessageForm = {
+ deleted,
+ edit_id,
+ auth: api.auth,
+ };
+
+ let deleteRes: PrivateMessageResponse = await fetch(
+ `${apiUrl(api)}/private_message/delete`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(deletePrivateMessageForm),
+ }
+ ).then(d => d.json());
+
+ return deleteRes;
+}
+
+export async function listPrivateMessages(
+ api: API
+): Promise<PrivateMessagesResponse> {
+ let getPrivateMessagesUrl = `${apiUrl(api)}/private_message/list?auth=${
+ api.auth
+ }&unread_only=false&limit=999`;
+
+ let getPrivateMessagesRes: PrivateMessagesResponse = await fetch(
+ getPrivateMessagesUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+ return getPrivateMessagesRes;
+}
+
+export async function unfollowRemotes(
+ api: API
+): Promise<GetFollowedCommunitiesResponse> {
+ // Unfollow all remote communities
+ let followed = await checkFollowedCommunities(api);
+ let remoteFollowed = followed.communities.filter(
+ c => c.community_local == false
+ );
+ for (let cu of remoteFollowed) {
+ await followCommunity(api, false, cu.community_id);
+ }
+ let followed2 = await checkFollowedCommunities(api);
+ return followed2;
+}
+
+export async function followBeta(api: API): Promise<CommunityResponse> {
+ await unfollowRemotes(api);
+
+ // Cache it
+ let search = await searchForBetaCommunity(api);
+
+ // Unfollow first
+ let follow = await followCommunity(
+ api,
+ true,
+ search.communities.filter(c => c.local == false)[0].id
+ );
+ return follow;
+}
+
+export function wrapper(form: any): string {
+ return JSON.stringify(form);
+}
+
+function randomString(length: number): string {
+ var result = '';
+ var characters = 'abcdefghijklmnopqrstuvwxyz0123456789_';
+ var charactersLength = characters.length;
+ for (var i = 0; i < length; i++) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
+}