Add "subscribers-only" post visibility setting
This commit is contained in:
parent
71fc2d9dad
commit
9330038141
9 changed files with 194 additions and 29 deletions
|
@ -351,11 +351,7 @@ paths:
|
||||||
format: uuid
|
format: uuid
|
||||||
visibility:
|
visibility:
|
||||||
description: Visibility of the post.
|
description: Visibility of the post.
|
||||||
type: string
|
$ref: '#/components/schemas/Visibility'
|
||||||
enum:
|
|
||||||
- public
|
|
||||||
- private
|
|
||||||
- direct
|
|
||||||
mentions:
|
mentions:
|
||||||
description: Array of profile IDs to be mentioned
|
description: Array of profile IDs to be mentioned
|
||||||
type: array
|
type: array
|
||||||
|
@ -681,11 +677,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
visibility:
|
visibility:
|
||||||
description: Visibility of this post.
|
description: Visibility of this post.
|
||||||
type: string
|
$ref: '#/components/schemas/Visibility'
|
||||||
enum:
|
|
||||||
- public
|
|
||||||
- private
|
|
||||||
- direct
|
|
||||||
mentions:
|
mentions:
|
||||||
description: Mentions of users within the post.
|
description: Mentions of users within the post.
|
||||||
type: array
|
type: array
|
||||||
|
@ -712,3 +704,10 @@ components:
|
||||||
type: string
|
type: string
|
||||||
url:
|
url:
|
||||||
description: A link to the hashtag on the instance.
|
description: A link to the hashtag on the instance.
|
||||||
|
Visibility:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- public
|
||||||
|
- private
|
||||||
|
- subscribers
|
||||||
|
- direct
|
||||||
|
|
|
@ -11,7 +11,12 @@ use crate::utils::files::get_file_url;
|
||||||
use crate::utils::id::new_uuid;
|
use crate::utils::id::new_uuid;
|
||||||
use super::actor::{get_local_actor, ActorKeyError};
|
use super::actor::{get_local_actor, ActorKeyError};
|
||||||
use super::constants::{AP_CONTEXT, AP_PUBLIC};
|
use super::constants::{AP_CONTEXT, AP_PUBLIC};
|
||||||
use super::views::{get_actor_url, get_followers_url, get_object_url};
|
use super::views::{
|
||||||
|
get_actor_url,
|
||||||
|
get_followers_url,
|
||||||
|
get_subscribers_url,
|
||||||
|
get_object_url,
|
||||||
|
};
|
||||||
use super::vocabulary::*;
|
use super::vocabulary::*;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
|
@ -187,13 +192,22 @@ pub fn create_note(
|
||||||
let mut secondary_audience = vec![];
|
let mut secondary_audience = vec![];
|
||||||
let followers_collection_url =
|
let followers_collection_url =
|
||||||
get_followers_url(instance_url, &post.author.username);
|
get_followers_url(instance_url, &post.author.username);
|
||||||
let mut tags = vec![];
|
let subscribers_collection_url =
|
||||||
if matches!(post.visibility, Visibility::Public) {
|
get_subscribers_url(instance_url, &post.author.username);
|
||||||
primary_audience.push(AP_PUBLIC.to_string());
|
match post.visibility {
|
||||||
secondary_audience.push(followers_collection_url);
|
Visibility::Public => {
|
||||||
} else if matches!(post.visibility, Visibility::Followers) {
|
primary_audience.push(AP_PUBLIC.to_string());
|
||||||
primary_audience.push(followers_collection_url);
|
secondary_audience.push(followers_collection_url);
|
||||||
|
},
|
||||||
|
Visibility::Followers => {
|
||||||
|
primary_audience.push(followers_collection_url);
|
||||||
|
},
|
||||||
|
Visibility::Subscribers => {
|
||||||
|
primary_audience.push(subscribers_collection_url);
|
||||||
|
},
|
||||||
|
Visibility::Direct => (),
|
||||||
};
|
};
|
||||||
|
let mut tags = vec![];
|
||||||
for profile in &post.mentions {
|
for profile in &post.mentions {
|
||||||
let actor_id = profile.actor_id(instance_url);
|
let actor_id = profile.actor_id(instance_url);
|
||||||
primary_audience.push(actor_id);
|
primary_audience.push(actor_id);
|
||||||
|
|
|
@ -44,6 +44,10 @@ pub fn get_following_url(instance_url: &str, username: &str) -> String {
|
||||||
format!("{}/users/{}/following", instance_url, username)
|
format!("{}/users/{}/following", instance_url, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_subscribers_url(instance_url: &str, username: &str) -> String {
|
||||||
|
format!("{}/users/{}/subscribers", instance_url, username)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_instance_actor_url(instance_url: &str) -> String {
|
pub fn get_instance_actor_url(instance_url: &str) -> String {
|
||||||
format!("{}/actor", instance_url)
|
format!("{}/actor", instance_url)
|
||||||
}
|
}
|
||||||
|
@ -225,6 +229,24 @@ async fn following_collection(
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/subscribers")]
|
||||||
|
async fn subscribers_collection(
|
||||||
|
config: web::Data<Config>,
|
||||||
|
web::Path(username): web::Path<String>,
|
||||||
|
query_params: web::Query<CollectionQueryParams>,
|
||||||
|
) -> Result<HttpResponse, HttpError> {
|
||||||
|
if query_params.page.is_some() {
|
||||||
|
// Subscriber list is hidden
|
||||||
|
return Err(HttpError::PermissionError);
|
||||||
|
}
|
||||||
|
let collection_id = get_subscribers_url(&config.instance_url(), &username);
|
||||||
|
let collection = OrderedCollection::new(collection_id, None);
|
||||||
|
let response = HttpResponse::Ok()
|
||||||
|
.content_type(ACTIVITY_CONTENT_TYPE)
|
||||||
|
.json(collection);
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn actor_scope() -> Scope {
|
pub fn actor_scope() -> Scope {
|
||||||
web::scope("/users/{username}")
|
web::scope("/users/{username}")
|
||||||
.service(actor_view)
|
.service(actor_view)
|
||||||
|
@ -232,6 +254,7 @@ pub fn actor_scope() -> Scope {
|
||||||
.service(outbox)
|
.service(outbox)
|
||||||
.service(followers_collection)
|
.service(followers_collection)
|
||||||
.service(following_collection)
|
.service(following_collection)
|
||||||
|
.service(subscribers_collection)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("")]
|
#[get("")]
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::activitypub::actor::Actor;
|
||||||
use crate::errors::DatabaseError;
|
use crate::errors::DatabaseError;
|
||||||
use crate::models::posts::queries::get_post_author;
|
use crate::models::posts::queries::get_post_author;
|
||||||
use crate::models::posts::types::{Post, Visibility};
|
use crate::models::posts::types::{Post, Visibility};
|
||||||
use crate::models::relationships::queries::get_followers;
|
use crate::models::relationships::queries::{get_followers, get_subscribers};
|
||||||
use crate::models::users::types::User;
|
use crate::models::users::types::User;
|
||||||
|
|
||||||
pub async fn get_note_recipients(
|
pub async fn get_note_recipients(
|
||||||
|
@ -13,9 +13,16 @@ pub async fn get_note_recipients(
|
||||||
post: &Post,
|
post: &Post,
|
||||||
) -> Result<Vec<Actor>, DatabaseError> {
|
) -> Result<Vec<Actor>, DatabaseError> {
|
||||||
let mut audience = vec![];
|
let mut audience = vec![];
|
||||||
if matches!(post.visibility, Visibility::Public | Visibility::Followers) {
|
match post.visibility {
|
||||||
let followers = get_followers(db_client, ¤t_user.id, None, None).await?;
|
Visibility::Public | Visibility::Followers => {
|
||||||
audience.extend(followers);
|
let followers = get_followers(db_client, ¤t_user.id, None, None).await?;
|
||||||
|
audience.extend(followers);
|
||||||
|
},
|
||||||
|
Visibility::Subscribers => {
|
||||||
|
let subscribers = get_subscribers(db_client, ¤t_user.id).await?;
|
||||||
|
audience.extend(subscribers);
|
||||||
|
},
|
||||||
|
Visibility::Direct => (),
|
||||||
};
|
};
|
||||||
if let Some(in_reply_to_id) = post.in_reply_to_id {
|
if let Some(in_reply_to_id) = post.in_reply_to_id {
|
||||||
// TODO: use post.in_reply_to ?
|
// TODO: use post.in_reply_to ?
|
||||||
|
|
|
@ -98,6 +98,7 @@ impl Status {
|
||||||
Visibility::Public => "public",
|
Visibility::Public => "public",
|
||||||
Visibility::Direct => "direct",
|
Visibility::Direct => "direct",
|
||||||
Visibility::Followers => "private",
|
Visibility::Followers => "private",
|
||||||
|
Visibility::Subscribers => "subscribers",
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
id: post.id,
|
id: post.id,
|
||||||
|
@ -147,6 +148,7 @@ impl TryFrom<StatusData> for PostCreateData {
|
||||||
Some("public") => Visibility::Public,
|
Some("public") => Visibility::Public,
|
||||||
Some("direct") => Visibility::Direct,
|
Some("direct") => Visibility::Direct,
|
||||||
Some("private") => Visibility::Followers,
|
Some("private") => Visibility::Followers,
|
||||||
|
Some("subscribers") => Visibility::Subscribers,
|
||||||
Some(_) => return Err(ValidationError("invalid visibility parameter")),
|
Some(_) => return Err(ValidationError("invalid visibility parameter")),
|
||||||
None => Visibility::Public,
|
None => Visibility::Public,
|
||||||
};
|
};
|
||||||
|
|
|
@ -90,6 +90,20 @@ pub async fn can_view_post(
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Visibility::Subscribers => {
|
||||||
|
if let Some(user) = user {
|
||||||
|
let relationship = get_relationship(
|
||||||
|
db_client,
|
||||||
|
&post.author.id,
|
||||||
|
&user.id,
|
||||||
|
).await?;
|
||||||
|
let is_mentioned = post.mentions.iter()
|
||||||
|
.any(|profile| profile.id == user.profile.id);
|
||||||
|
relationship.subscription_from || is_mentioned
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
@ -99,7 +113,7 @@ mod tests {
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use tokio_postgres::Client;
|
use tokio_postgres::Client;
|
||||||
use crate::database::test_utils::create_test_database;
|
use crate::database::test_utils::create_test_database;
|
||||||
use crate::models::relationships::queries::follow;
|
use crate::models::relationships::queries::{follow, subscribe};
|
||||||
use crate::models::users::queries::create_user;
|
use crate::models::users::queries::create_user;
|
||||||
use crate::models::users::types::UserCreateData;
|
use crate::models::users::types::UserCreateData;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -180,4 +194,32 @@ mod tests {
|
||||||
let result = can_view_post(db_client, Some(&follower), &post).await.unwrap();
|
let result = can_view_post(db_client, Some(&follower), &post).await.unwrap();
|
||||||
assert_eq!(result, true);
|
assert_eq!(result, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_can_view_post_subscribers_only() {
|
||||||
|
let db_client = &mut create_test_database().await;
|
||||||
|
let author = create_test_user(db_client, "author").await;
|
||||||
|
let follower = create_test_user(db_client, "follower").await;
|
||||||
|
follow(db_client, &follower.id, &author.id).await.unwrap();
|
||||||
|
let subscriber = create_test_user(db_client, "subscriber").await;
|
||||||
|
subscribe(db_client, &subscriber.id, &author.id).await.unwrap();
|
||||||
|
let post = Post {
|
||||||
|
author: author.profile,
|
||||||
|
visibility: Visibility::Subscribers,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
can_view_post(db_client, None, &post).await.unwrap(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
can_view_post(db_client, Some(&follower), &post).await.unwrap(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
can_view_post(db_client, Some(&subscriber), &post).await.unwrap(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,11 +222,20 @@ fn build_visibility_filter() -> String {
|
||||||
AND target_id = post.author_id
|
AND target_id = post.author_id
|
||||||
AND relationship_type = {relationship_follow}
|
AND relationship_type = {relationship_follow}
|
||||||
)
|
)
|
||||||
|
OR post.visibility = {visibility_subscribers} AND EXISTS (
|
||||||
|
SELECT 1 FROM relationship
|
||||||
|
WHERE
|
||||||
|
source_id = $current_user_id
|
||||||
|
AND target_id = post.author_id
|
||||||
|
AND relationship_type = {relationship_subscription}
|
||||||
|
)
|
||||||
)",
|
)",
|
||||||
visibility_public=i16::from(&Visibility::Public),
|
visibility_public=i16::from(&Visibility::Public),
|
||||||
visibility_direct=i16::from(&Visibility::Direct),
|
visibility_direct=i16::from(&Visibility::Direct),
|
||||||
visibility_followers=i16::from(&Visibility::Followers),
|
visibility_followers=i16::from(&Visibility::Followers),
|
||||||
|
visibility_subscribers=i16::from(&Visibility::Subscribers),
|
||||||
relationship_follow=i16::from(&RelationshipType::Follow),
|
relationship_follow=i16::from(&RelationshipType::Follow),
|
||||||
|
relationship_subscription=i16::from(&RelationshipType::Subscription),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +245,8 @@ pub async fn get_home_timeline(
|
||||||
max_post_id: Option<Uuid>,
|
max_post_id: Option<Uuid>,
|
||||||
limit: i64,
|
limit: i64,
|
||||||
) -> Result<Vec<Post>, DatabaseError> {
|
) -> Result<Vec<Post>, DatabaseError> {
|
||||||
// Select posts from follows, posts where current user is mentioned
|
// Select posts from follows, subscriptions,
|
||||||
|
// posts where current user is mentioned
|
||||||
// and user's own posts.
|
// and user's own posts.
|
||||||
let statement = format!(
|
let statement = format!(
|
||||||
"
|
"
|
||||||
|
@ -252,7 +262,9 @@ pub async fn get_home_timeline(
|
||||||
post.author_id = $current_user_id
|
post.author_id = $current_user_id
|
||||||
OR EXISTS (
|
OR EXISTS (
|
||||||
SELECT 1 FROM relationship
|
SELECT 1 FROM relationship
|
||||||
WHERE source_id = $current_user_id AND target_id = post.author_id
|
WHERE
|
||||||
|
source_id = $current_user_id AND target_id = post.author_id
|
||||||
|
AND relationship_type IN ({relationship_follow}, {relationship_subscription})
|
||||||
)
|
)
|
||||||
OR EXISTS (
|
OR EXISTS (
|
||||||
SELECT 1 FROM mention
|
SELECT 1 FROM mention
|
||||||
|
@ -267,6 +279,8 @@ pub async fn get_home_timeline(
|
||||||
related_attachments=RELATED_ATTACHMENTS,
|
related_attachments=RELATED_ATTACHMENTS,
|
||||||
related_mentions=RELATED_MENTIONS,
|
related_mentions=RELATED_MENTIONS,
|
||||||
related_tags=RELATED_TAGS,
|
related_tags=RELATED_TAGS,
|
||||||
|
relationship_follow=i16::from(&RelationshipType::Follow),
|
||||||
|
relationship_subscription=i16::from(&RelationshipType::Subscription),
|
||||||
visibility_filter=build_visibility_filter(),
|
visibility_filter=build_visibility_filter(),
|
||||||
);
|
);
|
||||||
let query = query!(
|
let query = query!(
|
||||||
|
@ -780,7 +794,7 @@ mod tests {
|
||||||
use crate::database::test_utils::create_test_database;
|
use crate::database::test_utils::create_test_database;
|
||||||
use crate::models::profiles::queries::create_profile;
|
use crate::models::profiles::queries::create_profile;
|
||||||
use crate::models::profiles::types::ProfileCreateData;
|
use crate::models::profiles::types::ProfileCreateData;
|
||||||
use crate::models::relationships::queries::follow;
|
use crate::models::relationships::queries::{follow, subscribe};
|
||||||
use crate::models::users::queries::create_user;
|
use crate::models::users::queries::create_user;
|
||||||
use crate::models::users::types::UserCreateData;
|
use crate::models::users::types::UserCreateData;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -878,9 +892,29 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let post_8 = create_post(db_client, &user_2.id, post_data_8).await.unwrap();
|
let post_8 = create_post(db_client, &user_2.id, post_data_8).await.unwrap();
|
||||||
|
// Subscribers-only post by followed user
|
||||||
|
let post_data_9 = 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();
|
||||||
|
// Subscribers-only post by subscription
|
||||||
|
let user_data_3 = UserCreateData {
|
||||||
|
username: "subscription".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
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 {
|
||||||
|
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 timeline = get_home_timeline(db_client, ¤t_user.id, None, 10).await.unwrap();
|
let timeline = get_home_timeline(db_client, ¤t_user.id, None, 20).await.unwrap();
|
||||||
assert_eq!(timeline.len(), 5);
|
assert_eq!(timeline.len(), 6);
|
||||||
assert_eq!(timeline.iter().any(|post| post.id == post_1.id), true);
|
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_2.id), true);
|
||||||
assert_eq!(timeline.iter().any(|post| post.id == post_3.id), false);
|
assert_eq!(timeline.iter().any(|post| post.id == post_3.id), false);
|
||||||
|
@ -889,6 +923,8 @@ mod tests {
|
||||||
assert_eq!(timeline.iter().any(|post| post.id == post_6.id), true);
|
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_7.id), false);
|
||||||
assert_eq!(timeline.iter().any(|post| post.id == post_8.id), true);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -906,13 +942,27 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let post_1 = create_post(db_client, &user.id, post_data_1).await.unwrap();
|
let post_1 = create_post(db_client, &user.id, post_data_1).await.unwrap();
|
||||||
// Direct message
|
// Followers only post
|
||||||
let post_data_2 = PostCreateData {
|
let post_data_2 = PostCreateData {
|
||||||
|
content: "my post".to_string(),
|
||||||
|
visibility: Visibility::Followers,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let post_2 = create_post(db_client, &user.id, post_data_2).await.unwrap();
|
||||||
|
// Subscribers only post
|
||||||
|
let post_data_3 = PostCreateData {
|
||||||
|
content: "my post".to_string(),
|
||||||
|
visibility: Visibility::Subscribers,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let post_3 = create_post(db_client, &user.id, post_data_3).await.unwrap();
|
||||||
|
// Direct message
|
||||||
|
let post_data_4 = PostCreateData {
|
||||||
content: "my post".to_string(),
|
content: "my post".to_string(),
|
||||||
visibility: Visibility::Direct,
|
visibility: Visibility::Direct,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let post_2 = create_post(db_client, &user.id, post_data_2).await.unwrap();
|
let post_4 = create_post(db_client, &user.id, post_data_4).await.unwrap();
|
||||||
|
|
||||||
// Anonymous viewer
|
// Anonymous viewer
|
||||||
let timeline = get_posts_by_author(
|
let timeline = get_posts_by_author(
|
||||||
|
@ -921,5 +971,7 @@ mod tests {
|
||||||
assert_eq!(timeline.len(), 1);
|
assert_eq!(timeline.len(), 1);
|
||||||
assert_eq!(timeline.iter().any(|post| post.id == post_1.id), true);
|
assert_eq!(timeline.iter().any(|post| post.id == post_1.id), true);
|
||||||
assert_eq!(timeline.iter().any(|post| post.id == post_2.id), false);
|
assert_eq!(timeline.iter().any(|post| post.id == post_2.id), false);
|
||||||
|
assert_eq!(timeline.iter().any(|post| post.id == post_3.id), false);
|
||||||
|
assert_eq!(timeline.iter().any(|post| post.id == post_4.id), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub enum Visibility {
|
||||||
Public,
|
Public,
|
||||||
Direct,
|
Direct,
|
||||||
Followers,
|
Followers,
|
||||||
|
Subscribers,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Visibility {
|
impl Default for Visibility {
|
||||||
|
@ -29,6 +30,7 @@ impl From<&Visibility> for i16 {
|
||||||
Visibility::Public => 1,
|
Visibility::Public => 1,
|
||||||
Visibility::Direct => 2,
|
Visibility::Direct => 2,
|
||||||
Visibility::Followers => 3,
|
Visibility::Followers => 3,
|
||||||
|
Visibility::Subscribers => 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +43,7 @@ impl TryFrom<i16> for Visibility {
|
||||||
1 => Self::Public,
|
1 => Self::Public,
|
||||||
2 => Self::Direct,
|
2 => Self::Direct,
|
||||||
3 => Self::Followers,
|
3 => Self::Followers,
|
||||||
|
4 => Self::Subscribers,
|
||||||
_ => return Err(ConversionError),
|
_ => return Err(ConversionError),
|
||||||
};
|
};
|
||||||
Ok(visibility)
|
Ok(visibility)
|
||||||
|
|
|
@ -367,6 +367,29 @@ pub async fn unsubscribe(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_subscribers(
|
||||||
|
db_client: &impl GenericClient,
|
||||||
|
profile_id: &Uuid,
|
||||||
|
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
||||||
|
let rows = db_client.query(
|
||||||
|
"
|
||||||
|
SELECT actor_profile
|
||||||
|
FROM actor_profile
|
||||||
|
JOIN relationship
|
||||||
|
ON (actor_profile.id = relationship.source_id)
|
||||||
|
WHERE
|
||||||
|
relationship.target_id = $1
|
||||||
|
AND relationship.relationship_type = $2
|
||||||
|
ORDER BY relationship.id DESC
|
||||||
|
",
|
||||||
|
&[&profile_id, &RelationshipType::Subscription],
|
||||||
|
).await?;
|
||||||
|
let profiles = rows.iter()
|
||||||
|
.map(|row| row.try_get("actor_profile"))
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
Ok(profiles)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
|
Loading…
Reference in a new issue