Allow to hide reposts made by given actor from home timeline
This commit is contained in:
parent
0f47fa201d
commit
f14e762ee3
7 changed files with 184 additions and 23 deletions
|
@ -249,6 +249,16 @@ paths:
|
|||
- tokenAuth: []
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/account_id'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
reblogs:
|
||||
description: Receive this actor's reposts in home timeline?
|
||||
type: boolean
|
||||
default: true
|
||||
responses:
|
||||
200:
|
||||
description: Successfully followed, or actor was already followed
|
||||
|
@ -687,18 +697,27 @@ components:
|
|||
following:
|
||||
description: Are you following this user?
|
||||
type: boolean
|
||||
default: false
|
||||
followed_by:
|
||||
description: Are you followed by this user?
|
||||
type: boolean
|
||||
default: false
|
||||
requested:
|
||||
description: Do you have a pending follow request for this user?
|
||||
type: boolean
|
||||
default: false
|
||||
subscription_to:
|
||||
description: Are you sending subscription payments to this user?
|
||||
type: boolean
|
||||
default: false
|
||||
subscription_from:
|
||||
description: Are you receiving subscription payments from this user?
|
||||
type: boolean
|
||||
default: false
|
||||
showing_reblogs:
|
||||
description: Are you receiving this user's boosts in your home timeline?
|
||||
type: boolean
|
||||
default: true
|
||||
Signature:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -35,6 +35,11 @@ pub async fn get_relationship(
|
|||
relationship_map.subscription_from = true;
|
||||
};
|
||||
},
|
||||
RelationshipType::HideReposts => {
|
||||
if relationship.is_direct(source_id, target_id)? {
|
||||
relationship_map.showing_reblogs = false;
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
Ok(relationship_map)
|
||||
|
@ -48,6 +53,8 @@ mod tests {
|
|||
create_follow_request,
|
||||
follow,
|
||||
follow_request_accepted,
|
||||
hide_reposts,
|
||||
show_reposts,
|
||||
subscribe,
|
||||
unfollow,
|
||||
unsubscribe,
|
||||
|
@ -85,6 +92,7 @@ mod tests {
|
|||
assert_eq!(relationship.requested, false);
|
||||
assert_eq!(relationship.subscription_to, false);
|
||||
assert_eq!(relationship.subscription_from, false);
|
||||
assert_eq!(relationship.showing_reblogs, true);
|
||||
// Follow request
|
||||
let follow_request = create_follow_request(db_client, &user_1.id, &user_2.id).await.unwrap();
|
||||
let relationship = get_relationship(db_client, &user_1.id, &user_2.id).await.unwrap();
|
||||
|
@ -122,4 +130,25 @@ mod tests {
|
|||
assert_eq!(relationship.subscription_to, false);
|
||||
assert_eq!(relationship.subscription_from, false);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_hide_reblogs() {
|
||||
let db_client = &mut create_test_database().await;
|
||||
let (user_1, user_2) = create_users(db_client).await.unwrap();
|
||||
follow(db_client, &user_1.id, &user_2.id).await.unwrap();
|
||||
let relationship = get_relationship(db_client, &user_1.id, &user_2.id).await.unwrap();
|
||||
assert_eq!(relationship.following, true);
|
||||
assert_eq!(relationship.showing_reblogs, true);
|
||||
|
||||
hide_reposts(db_client, &user_1.id, &user_2.id).await.unwrap();
|
||||
let relationship = get_relationship(db_client, &user_1.id, &user_2.id).await.unwrap();
|
||||
assert_eq!(relationship.following, true);
|
||||
assert_eq!(relationship.showing_reblogs, false);
|
||||
|
||||
show_reposts(db_client, &user_1.id, &user_2.id).await.unwrap();
|
||||
let relationship = get_relationship(db_client, &user_1.id, &user_2.id).await.unwrap();
|
||||
assert_eq!(relationship.following, true);
|
||||
assert_eq!(relationship.showing_reblogs, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,7 +193,7 @@ pub struct RelationshipQueryParams {
|
|||
pub id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize)]
|
||||
#[derive(Serialize)]
|
||||
pub struct RelationshipMap {
|
||||
pub id: Uuid, // target ID
|
||||
pub following: bool,
|
||||
|
@ -201,6 +201,29 @@ pub struct RelationshipMap {
|
|||
pub requested: bool,
|
||||
pub subscription_to: bool,
|
||||
pub subscription_from: bool,
|
||||
pub showing_reblogs: bool,
|
||||
}
|
||||
|
||||
fn default_showing_reblogs() -> bool { true }
|
||||
|
||||
impl Default for RelationshipMap {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: Default::default(),
|
||||
following: false,
|
||||
followed_by: false,
|
||||
requested: false,
|
||||
subscription_to: false,
|
||||
subscription_from: false,
|
||||
showing_reblogs: default_showing_reblogs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FollowData {
|
||||
#[serde(default = "default_showing_reblogs")]
|
||||
pub reblogs: bool,
|
||||
}
|
||||
|
||||
fn default_page_size() -> i64 { 20 }
|
||||
|
@ -216,7 +239,6 @@ pub struct StatusListQueryParams {
|
|||
pub limit: i64,
|
||||
}
|
||||
|
||||
|
||||
fn default_follow_list_page_size() -> i64 { 40 }
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
|
@ -33,6 +33,8 @@ use crate::models::relationships::queries::{
|
|||
get_follow_request_by_path,
|
||||
get_followers,
|
||||
get_following,
|
||||
hide_reposts,
|
||||
show_reposts,
|
||||
unfollow,
|
||||
};
|
||||
use crate::models::users::queries::{
|
||||
|
@ -51,6 +53,7 @@ use super::types::{
|
|||
Account,
|
||||
AccountCreateData,
|
||||
AccountUpdateData,
|
||||
FollowData,
|
||||
FollowListQueryParams,
|
||||
RelationshipQueryParams,
|
||||
StatusListQueryParams,
|
||||
|
@ -249,12 +252,13 @@ async fn follow_account(
|
|||
config: web::Data<Config>,
|
||||
db_pool: web::Data<Pool>,
|
||||
web::Path(account_id): web::Path<Uuid>,
|
||||
data: web::Json<FollowData>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||
let target = get_profile_by_id(db_client, &account_id).await?;
|
||||
if let Some(remote_actor) = target.actor_json {
|
||||
// Remote follow
|
||||
// Create follow request if target is remote
|
||||
match create_follow_request(db_client, ¤t_user.id, &target.id).await {
|
||||
Ok(request) => {
|
||||
let activity = create_activity_follow(
|
||||
|
@ -275,6 +279,11 @@ async fn follow_account(
|
|||
Err(other_error) => return Err(other_error.into()),
|
||||
};
|
||||
};
|
||||
if data.reblogs {
|
||||
show_reposts(db_client, ¤t_user.id, &target.id).await?;
|
||||
} else {
|
||||
hide_reposts(db_client, ¤t_user.id, &target.id).await?;
|
||||
};
|
||||
let relationship = get_relationship(
|
||||
db_client,
|
||||
¤t_user.id,
|
||||
|
@ -294,7 +303,7 @@ async fn unfollow_account(
|
|||
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||
let target = get_profile_by_id(db_client, &account_id).await?;
|
||||
if let Some(remote_actor) = target.actor_json {
|
||||
// Remote follow
|
||||
// Get follow request ID then unfollow and delete it
|
||||
match get_follow_request_by_path(
|
||||
db_client,
|
||||
¤t_user.id,
|
||||
|
|
|
@ -248,6 +248,7 @@ pub async fn get_home_timeline(
|
|||
// Select posts from follows, subscriptions,
|
||||
// posts where current user is mentioned
|
||||
// and user's own posts.
|
||||
// Select reposts if they are not hidden.
|
||||
let statement = format!(
|
||||
"
|
||||
SELECT
|
||||
|
@ -260,11 +261,29 @@ pub async fn get_home_timeline(
|
|||
WHERE
|
||||
(
|
||||
post.author_id = $current_user_id
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM relationship
|
||||
WHERE
|
||||
source_id = $current_user_id AND target_id = post.author_id
|
||||
AND relationship_type IN ({relationship_follow}, {relationship_subscription})
|
||||
OR (
|
||||
EXISTS (
|
||||
SELECT 1 FROM relationship
|
||||
WHERE
|
||||
source_id = $current_user_id
|
||||
AND target_id = post.author_id
|
||||
AND relationship_type IN ({relationship_follow}, {relationship_subscription})
|
||||
)
|
||||
AND (
|
||||
post.repost_of_id IS NULL
|
||||
OR NOT EXISTS (
|
||||
SELECT 1 FROM relationship
|
||||
WHERE
|
||||
source_id = $current_user_id
|
||||
AND target_id = post.author_id
|
||||
AND relationship_type = {relationship_hide_reposts}
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM post AS repost_of
|
||||
WHERE repost_of.id = post.repost_of_id
|
||||
AND repost_of.author_id = $current_user_id
|
||||
)
|
||||
)
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM mention
|
||||
|
@ -281,6 +300,7 @@ pub async fn get_home_timeline(
|
|||
related_tags=RELATED_TAGS,
|
||||
relationship_follow=i16::from(&RelationshipType::Follow),
|
||||
relationship_subscription=i16::from(&RelationshipType::Subscription),
|
||||
relationship_hide_reposts=i16::from(&RelationshipType::HideReposts),
|
||||
visibility_filter=build_visibility_filter(),
|
||||
);
|
||||
let query = query!(
|
||||
|
@ -890,7 +910,11 @@ mod tests {
|
|||
use crate::database::test_utils::create_test_database;
|
||||
use crate::models::profiles::queries::create_profile;
|
||||
use crate::models::profiles::types::ProfileCreateData;
|
||||
use crate::models::relationships::queries::{follow, subscribe};
|
||||
use crate::models::relationships::queries::{
|
||||
follow,
|
||||
hide_reposts,
|
||||
subscribe,
|
||||
};
|
||||
use crate::models::users::queries::create_user;
|
||||
use crate::models::users::types::UserCreateData;
|
||||
use super::*;
|
||||
|
@ -973,28 +997,34 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
let post_6 = create_post(db_client, &user_2.id, post_data_6).await.unwrap();
|
||||
// Direct message from followed user sent to another user
|
||||
// Followed user's repost
|
||||
let post_data_7 = PostCreateData {
|
||||
repost_of_id: Some(post_3.id),
|
||||
..Default::default()
|
||||
};
|
||||
let post_7 = create_post(db_client, &user_2.id, post_data_7).await.unwrap();
|
||||
// Direct message from followed user sent to another user
|
||||
let post_data_8 = PostCreateData {
|
||||
content: "test post".to_string(),
|
||||
visibility: Visibility::Direct,
|
||||
mentions: vec![user_1.id],
|
||||
..Default::default()
|
||||
};
|
||||
let post_7 = create_post(db_client, &user_2.id, post_data_7).await.unwrap();
|
||||
let post_8 = create_post(db_client, &user_2.id, post_data_8).await.unwrap();
|
||||
// Followers-only post from followed user
|
||||
let post_data_8 = PostCreateData {
|
||||
let post_data_9 = PostCreateData {
|
||||
content: "followers only".to_string(),
|
||||
visibility: Visibility::Followers,
|
||||
..Default::default()
|
||||
};
|
||||
let post_8 = create_post(db_client, &user_2.id, post_data_8).await.unwrap();
|
||||
let post_9 = create_post(db_client, &user_2.id, post_data_9).await.unwrap();
|
||||
// Subscribers-only post by followed user
|
||||
let post_data_9 = PostCreateData {
|
||||
let post_data_10 = PostCreateData {
|
||||
content: "subscribers only".to_string(),
|
||||
visibility: Visibility::Subscribers,
|
||||
..Default::default()
|
||||
};
|
||||
let post_9 = create_post(db_client, &user_2.id, post_data_9).await.unwrap();
|
||||
let post_10 = create_post(db_client, &user_2.id, post_data_10).await.unwrap();
|
||||
// Subscribers-only post by subscription
|
||||
let user_data_3 = UserCreateData {
|
||||
username: "subscription".to_string(),
|
||||
|
@ -1002,25 +1032,40 @@ mod tests {
|
|||
};
|
||||
let user_3 = create_user(db_client, user_data_3).await.unwrap();
|
||||
subscribe(db_client, ¤t_user.id, &user_3.id).await.unwrap();
|
||||
let post_data_10 = PostCreateData {
|
||||
let post_data_11 = PostCreateData {
|
||||
content: "subscribers only".to_string(),
|
||||
visibility: Visibility::Subscribers,
|
||||
..Default::default()
|
||||
};
|
||||
let post_10 = create_post(db_client, &user_3.id, post_data_10).await.unwrap();
|
||||
let post_11 = create_post(db_client, &user_3.id, post_data_11).await.unwrap();
|
||||
// Repost from followed user if hiding reposts
|
||||
let user_data_4 = UserCreateData {
|
||||
username: "hide reposts".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let user_4 = create_user(db_client, user_data_4).await.unwrap();
|
||||
follow(db_client, ¤t_user.id, &user_4.id).await.unwrap();
|
||||
hide_reposts(db_client, ¤t_user.id, &user_4.id).await.unwrap();
|
||||
let post_data_12 = PostCreateData {
|
||||
repost_of_id: Some(post_3.id),
|
||||
..Default::default()
|
||||
};
|
||||
let post_12 = create_post(db_client, &user_4.id, post_data_12).await.unwrap();
|
||||
|
||||
let timeline = get_home_timeline(db_client, ¤t_user.id, None, 20).await.unwrap();
|
||||
assert_eq!(timeline.len(), 6);
|
||||
assert_eq!(timeline.len(), 7);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_1.id), true);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_2.id), true);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_3.id), false);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_4.id), true);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_5.id), false);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_6.id), true);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_7.id), false);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_8.id), true);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_9.id), false);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_10.id), true);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_7.id), true);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_8.id), false);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_9.id), true);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_10.id), false);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_11.id), true);
|
||||
assert_eq!(timeline.iter().any(|post| post.id == post_12.id), false);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
@ -377,3 +377,37 @@ pub async fn get_subscribers(
|
|||
.collect::<Result<_, _>>()?;
|
||||
Ok(profiles)
|
||||
}
|
||||
|
||||
pub async fn hide_reposts(
|
||||
db_client: &impl GenericClient,
|
||||
source_id: &Uuid,
|
||||
target_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
db_client.execute(
|
||||
"
|
||||
INSERT INTO relationship (source_id, target_id, relationship_type)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (source_id, target_id, relationship_type) DO NOTHING
|
||||
",
|
||||
&[&source_id, &target_id, &RelationshipType::HideReposts],
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn show_reposts(
|
||||
db_client: &impl GenericClient,
|
||||
source_id: &Uuid,
|
||||
target_id: &Uuid,
|
||||
) -> Result<(), DatabaseError> {
|
||||
// Does not return NotFound error
|
||||
db_client.execute(
|
||||
"
|
||||
DELETE FROM relationship
|
||||
WHERE
|
||||
source_id = $1 AND target_id = $2
|
||||
AND relationship_type = $3
|
||||
",
|
||||
&[&source_id, &target_id, &RelationshipType::HideReposts],
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ pub enum RelationshipType {
|
|||
Follow,
|
||||
FollowRequest,
|
||||
Subscription,
|
||||
HideReposts,
|
||||
}
|
||||
|
||||
impl From<&RelationshipType> for i16 {
|
||||
|
@ -20,6 +21,7 @@ impl From<&RelationshipType> for i16 {
|
|||
RelationshipType::Follow => 1,
|
||||
RelationshipType::FollowRequest => 2,
|
||||
RelationshipType::Subscription => 3,
|
||||
RelationshipType::HideReposts => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +34,7 @@ impl TryFrom<i16> for RelationshipType {
|
|||
1 => Self::Follow,
|
||||
2 => Self::FollowRequest,
|
||||
3 => Self::Subscription,
|
||||
4 => Self::HideReposts,
|
||||
_ => return Err(ConversionError),
|
||||
};
|
||||
Ok(relationship_type)
|
||||
|
|
Loading…
Reference in a new issue