2021-04-09 00:22:17 +00:00
|
|
|
use std::convert::TryFrom;
|
|
|
|
|
|
|
|
use chrono::Utc;
|
|
|
|
use tokio_postgres::GenericClient;
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
2021-11-24 16:39:30 +00:00
|
|
|
use crate::database::catch_unique_violation;
|
2022-01-07 16:33:23 +00:00
|
|
|
use crate::database::query_macro::query;
|
2021-04-09 00:22:17 +00:00
|
|
|
use crate::errors::DatabaseError;
|
|
|
|
use crate::models::attachments::types::DbMediaAttachment;
|
2021-09-29 11:43:45 +00:00
|
|
|
use crate::models::cleanup::{
|
|
|
|
find_orphaned_files,
|
|
|
|
find_orphaned_ipfs_objects,
|
|
|
|
DeletionQueue,
|
|
|
|
};
|
2021-11-30 22:03:07 +00:00
|
|
|
use crate::models::notifications::queries::{
|
|
|
|
create_mention_notification,
|
|
|
|
create_reply_notification,
|
2021-11-30 22:18:20 +00:00
|
|
|
create_repost_notification,
|
2021-11-30 22:03:07 +00:00
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
use crate::models::profiles::queries::update_post_count;
|
2021-11-11 19:10:28 +00:00
|
|
|
use crate::models::profiles::types::DbActorProfile;
|
2022-02-03 00:15:45 +00:00
|
|
|
use crate::models::relationships::types::RelationshipType;
|
2021-12-01 23:26:59 +00:00
|
|
|
use crate::utils::id::new_uuid;
|
2021-11-19 00:32:22 +00:00
|
|
|
use super::types::{DbPost, Post, PostCreateData, Visibility};
|
2021-04-09 00:22:17 +00:00
|
|
|
|
|
|
|
pub async fn create_post(
|
|
|
|
db_client: &mut impl GenericClient,
|
|
|
|
author_id: &Uuid,
|
|
|
|
data: PostCreateData,
|
|
|
|
) -> Result<Post, DatabaseError> {
|
|
|
|
let transaction = db_client.transaction().await?;
|
2021-12-01 23:26:59 +00:00
|
|
|
let post_id = new_uuid();
|
2021-04-09 00:22:17 +00:00
|
|
|
let created_at = data.created_at.unwrap_or(Utc::now());
|
2021-11-24 16:39:30 +00:00
|
|
|
// Reposting of other reposts or non-public posts is not allowed
|
|
|
|
let insert_statement = format!(
|
2021-04-09 00:22:17 +00:00
|
|
|
"
|
2021-09-22 16:32:44 +00:00
|
|
|
INSERT INTO post (
|
|
|
|
id, author_id, content,
|
|
|
|
in_reply_to_id,
|
2021-11-24 16:39:30 +00:00
|
|
|
repost_of_id,
|
2021-11-18 21:48:49 +00:00
|
|
|
visibility,
|
2021-10-10 00:46:23 +00:00
|
|
|
object_id,
|
2021-09-22 16:32:44 +00:00
|
|
|
created_at
|
|
|
|
)
|
2021-11-24 16:39:30 +00:00
|
|
|
SELECT $1, $2, $3, $4, $5, $6, $7, $8
|
|
|
|
WHERE NOT EXISTS (
|
|
|
|
SELECT 1 FROM post
|
|
|
|
WHERE post.id = $5 AND (
|
|
|
|
post.repost_of_id IS NOT NULL
|
|
|
|
OR post.visibility != {visibility_public}
|
|
|
|
)
|
|
|
|
)
|
2021-04-09 00:22:17 +00:00
|
|
|
RETURNING post
|
|
|
|
",
|
2021-11-24 16:39:30 +00:00
|
|
|
visibility_public=i16::from(&Visibility::Public),
|
|
|
|
);
|
|
|
|
let maybe_post_row = transaction.query_opt(
|
|
|
|
insert_statement.as_str(),
|
2021-09-22 16:32:44 +00:00
|
|
|
&[
|
|
|
|
&post_id,
|
|
|
|
&author_id,
|
|
|
|
&data.content,
|
|
|
|
&data.in_reply_to_id,
|
2021-11-24 16:39:30 +00:00
|
|
|
&data.repost_of_id,
|
2021-11-18 21:48:49 +00:00
|
|
|
&data.visibility,
|
2021-10-10 00:46:23 +00:00
|
|
|
&data.object_id,
|
2021-09-22 16:32:44 +00:00
|
|
|
&created_at,
|
|
|
|
],
|
2021-11-24 16:39:30 +00:00
|
|
|
).await.map_err(catch_unique_violation("post"))?;
|
|
|
|
let post_row = maybe_post_row.ok_or(DatabaseError::NotFound("post"))?;
|
2021-09-24 14:02:56 +00:00
|
|
|
let db_post: DbPost = post_row.try_get("post")?;
|
|
|
|
// Create links to attachments
|
2021-09-25 21:51:28 +00:00
|
|
|
let attachments_rows = transaction.query(
|
2021-04-09 00:22:17 +00:00
|
|
|
"
|
|
|
|
UPDATE media_attachment
|
|
|
|
SET post_id = $1
|
2021-09-25 21:51:28 +00:00
|
|
|
WHERE owner_id = $2 AND id = ANY($3)
|
2021-04-09 00:22:17 +00:00
|
|
|
RETURNING media_attachment
|
|
|
|
",
|
2021-09-25 21:51:28 +00:00
|
|
|
&[&post_id, &author_id, &data.attachments],
|
2021-04-09 00:22:17 +00:00
|
|
|
).await?;
|
2021-09-25 21:51:28 +00:00
|
|
|
if attachments_rows.len() != data.attachments.len() {
|
|
|
|
// Some attachments were not found
|
|
|
|
return Err(DatabaseError::NotFound("attachment"));
|
|
|
|
}
|
|
|
|
let db_attachments: Vec<DbMediaAttachment> = attachments_rows.iter()
|
|
|
|
.map(|row| row.try_get("media_attachment"))
|
2021-04-09 00:22:17 +00:00
|
|
|
.collect::<Result<_, _>>()?;
|
2021-11-11 19:10:28 +00:00
|
|
|
// Create mentions
|
|
|
|
let mentions_rows = transaction.query(
|
|
|
|
"
|
|
|
|
INSERT INTO mention (post_id, profile_id)
|
|
|
|
SELECT $1, unnest($2::uuid[])
|
|
|
|
RETURNING (
|
|
|
|
SELECT actor_profile FROM actor_profile
|
|
|
|
WHERE actor_profile.id = profile_id
|
|
|
|
) AS actor_profile
|
|
|
|
",
|
|
|
|
&[&db_post.id, &data.mentions],
|
|
|
|
).await?;
|
|
|
|
if mentions_rows.len() != data.mentions.len() {
|
|
|
|
// Some profiles were not found
|
|
|
|
return Err(DatabaseError::NotFound("profile"));
|
|
|
|
};
|
|
|
|
let db_mentions: Vec<DbActorProfile> = mentions_rows.iter()
|
|
|
|
.map(|row| row.try_get("actor_profile"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
2021-12-07 23:28:58 +00:00
|
|
|
// Create tags
|
|
|
|
transaction.execute(
|
|
|
|
"
|
|
|
|
INSERT INTO tag (tag_name)
|
|
|
|
SELECT unnest($1::text[])
|
|
|
|
ON CONFLICT (tag_name) DO NOTHING
|
|
|
|
",
|
|
|
|
&[&data.tags],
|
|
|
|
).await?;
|
|
|
|
let tags_rows = transaction.query(
|
|
|
|
"
|
|
|
|
INSERT INTO post_tag (post_id, tag_id)
|
|
|
|
SELECT $1, tag.id FROM tag WHERE tag_name = ANY($2)
|
|
|
|
RETURNING (SELECT tag_name FROM tag WHERE tag.id = tag_id)
|
|
|
|
",
|
|
|
|
&[&db_post.id, &data.tags],
|
|
|
|
).await?;
|
|
|
|
if tags_rows.len() != data.tags.len() {
|
|
|
|
return Err(DatabaseError::NotFound("tag"));
|
|
|
|
};
|
|
|
|
let db_tags: Vec<String> = tags_rows.iter()
|
|
|
|
.map(|row| row.try_get("tag_name"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
2021-09-24 14:02:56 +00:00
|
|
|
// Update counters
|
2021-04-09 00:22:17 +00:00
|
|
|
let author = update_post_count(&transaction, &db_post.author_id, 1).await?;
|
2021-11-30 22:03:07 +00:00
|
|
|
let mut notified_users = vec![];
|
2021-09-24 14:02:56 +00:00
|
|
|
if let Some(in_reply_to_id) = &db_post.in_reply_to_id {
|
|
|
|
update_reply_count(&transaction, in_reply_to_id, 1).await?;
|
2021-12-04 22:48:00 +00:00
|
|
|
let in_reply_to_author = get_post_author(&transaction, in_reply_to_id).await?;
|
|
|
|
if in_reply_to_author.is_local() &&
|
|
|
|
in_reply_to_author.id != db_post.author_id
|
2021-11-21 14:38:27 +00:00
|
|
|
{
|
2021-10-28 23:26:31 +00:00
|
|
|
create_reply_notification(
|
|
|
|
&transaction,
|
|
|
|
&db_post.author_id,
|
2021-12-04 22:48:00 +00:00
|
|
|
&in_reply_to_author.id,
|
2021-10-28 23:26:31 +00:00
|
|
|
&db_post.id,
|
|
|
|
).await?;
|
2021-12-04 22:48:00 +00:00
|
|
|
notified_users.push(in_reply_to_author.id);
|
|
|
|
};
|
2021-09-24 14:02:56 +00:00
|
|
|
}
|
2021-11-24 16:39:30 +00:00
|
|
|
if let Some(repost_of_id) = &db_post.repost_of_id {
|
|
|
|
update_repost_count(&transaction, repost_of_id, 1).await?;
|
2021-12-04 22:48:00 +00:00
|
|
|
let repost_of_author = get_post_author(&transaction, repost_of_id).await?;
|
|
|
|
if repost_of_author.is_local() &&
|
|
|
|
repost_of_author.id != db_post.author_id &&
|
|
|
|
!notified_users.contains(&repost_of_author.id)
|
2021-11-30 22:18:20 +00:00
|
|
|
{
|
|
|
|
create_repost_notification(
|
|
|
|
&transaction,
|
|
|
|
&db_post.author_id,
|
2021-12-04 22:48:00 +00:00
|
|
|
&repost_of_author.id,
|
2021-12-10 15:31:57 +00:00
|
|
|
repost_of_id,
|
2021-11-30 22:18:20 +00:00
|
|
|
).await?;
|
2021-12-04 22:48:00 +00:00
|
|
|
notified_users.push(repost_of_author.id);
|
2021-11-30 22:18:20 +00:00
|
|
|
};
|
2021-11-24 16:39:30 +00:00
|
|
|
};
|
2021-11-30 22:03:07 +00:00
|
|
|
// Notify mentioned users
|
|
|
|
for profile in db_mentions.iter() {
|
|
|
|
if profile.is_local() &&
|
|
|
|
profile.id != db_post.author_id &&
|
|
|
|
// Don't send mention notification to the author of parent post
|
2021-11-30 22:18:20 +00:00
|
|
|
// or to the author of reposted post
|
2021-11-30 22:03:07 +00:00
|
|
|
!notified_users.contains(&profile.id)
|
|
|
|
{
|
|
|
|
create_mention_notification(
|
|
|
|
&transaction,
|
|
|
|
&db_post.author_id,
|
|
|
|
&profile.id,
|
|
|
|
&db_post.id,
|
|
|
|
).await?;
|
|
|
|
};
|
|
|
|
};
|
2021-09-24 14:02:56 +00:00
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
transaction.commit().await?;
|
2021-12-07 23:28:58 +00:00
|
|
|
let post = Post::new(db_post, author, db_attachments, db_mentions, db_tags)?;
|
2021-04-09 00:22:17 +00:00
|
|
|
Ok(post)
|
|
|
|
}
|
|
|
|
|
2021-12-01 21:58:18 +00:00
|
|
|
pub const RELATED_ATTACHMENTS: &str =
|
|
|
|
"ARRAY(
|
|
|
|
SELECT media_attachment
|
|
|
|
FROM media_attachment WHERE post_id = post.id
|
|
|
|
) AS attachments";
|
|
|
|
|
|
|
|
pub const RELATED_MENTIONS: &str =
|
|
|
|
"ARRAY(
|
|
|
|
SELECT actor_profile
|
|
|
|
FROM mention
|
|
|
|
JOIN actor_profile ON mention.profile_id = actor_profile.id
|
|
|
|
WHERE post_id = post.id
|
|
|
|
) AS mentions";
|
|
|
|
|
2021-12-07 23:28:58 +00:00
|
|
|
pub const RELATED_TAGS: &str =
|
|
|
|
"ARRAY(
|
|
|
|
SELECT tag.tag_name FROM tag
|
|
|
|
JOIN post_tag ON post_tag.tag_id = tag.id
|
|
|
|
WHERE post_tag.post_id = post.id
|
|
|
|
) AS tags";
|
|
|
|
|
2022-01-08 11:20:48 +00:00
|
|
|
fn build_visibility_filter() -> String {
|
|
|
|
format!(
|
|
|
|
"(
|
2022-01-05 18:47:02 +00:00
|
|
|
post.author_id = $current_user_id
|
|
|
|
OR post.visibility = {visibility_public}
|
|
|
|
OR post.visibility = {visibility_direct} AND EXISTS (
|
2022-01-08 11:20:48 +00:00
|
|
|
SELECT 1 FROM mention
|
|
|
|
WHERE post_id = post.id AND profile_id = $current_user_id
|
|
|
|
)
|
2022-01-05 18:47:02 +00:00
|
|
|
OR post.visibility = {visibility_followers} AND EXISTS (
|
|
|
|
SELECT 1 FROM relationship
|
2022-02-03 00:15:45 +00:00
|
|
|
WHERE
|
|
|
|
source_id = $current_user_id
|
|
|
|
AND target_id = post.author_id
|
|
|
|
AND relationship_type = {relationship_follow}
|
2022-01-05 18:47:02 +00:00
|
|
|
)
|
2022-01-08 11:20:48 +00:00
|
|
|
)",
|
|
|
|
visibility_public=i16::from(&Visibility::Public),
|
2022-01-05 18:47:02 +00:00
|
|
|
visibility_direct=i16::from(&Visibility::Direct),
|
|
|
|
visibility_followers=i16::from(&Visibility::Followers),
|
2022-02-03 00:15:45 +00:00
|
|
|
relationship_follow=i16::from(&RelationshipType::Follow),
|
2022-01-08 11:20:48 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-12-01 21:58:18 +00:00
|
|
|
pub async fn get_home_timeline(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
current_user_id: &Uuid,
|
2021-12-01 21:55:38 +00:00
|
|
|
max_post_id: Option<Uuid>,
|
2021-12-14 16:16:30 +00:00
|
|
|
limit: i64,
|
2021-12-01 21:58:18 +00:00
|
|
|
) -> Result<Vec<Post>, DatabaseError> {
|
2022-01-08 11:20:48 +00:00
|
|
|
// Select posts from follows, posts where current user is mentioned
|
|
|
|
// and user's own posts.
|
2021-12-01 21:58:18 +00:00
|
|
|
let statement = format!(
|
|
|
|
"
|
|
|
|
SELECT
|
|
|
|
post, actor_profile,
|
|
|
|
{related_attachments},
|
2021-12-07 23:28:58 +00:00
|
|
|
{related_mentions},
|
|
|
|
{related_tags}
|
2021-12-01 21:58:18 +00:00
|
|
|
FROM post
|
|
|
|
JOIN actor_profile ON post.author_id = actor_profile.id
|
|
|
|
WHERE
|
|
|
|
(
|
2022-01-07 16:33:23 +00:00
|
|
|
post.author_id = $current_user_id
|
2021-12-01 21:58:18 +00:00
|
|
|
OR EXISTS (
|
|
|
|
SELECT 1 FROM relationship
|
2022-01-07 16:33:23 +00:00
|
|
|
WHERE source_id = $current_user_id AND target_id = post.author_id
|
2021-12-01 21:58:18 +00:00
|
|
|
)
|
|
|
|
OR EXISTS (
|
|
|
|
SELECT 1 FROM mention
|
2022-01-07 16:33:23 +00:00
|
|
|
WHERE post_id = post.id AND profile_id = $current_user_id
|
2021-12-01 21:58:18 +00:00
|
|
|
)
|
|
|
|
)
|
2022-01-08 11:20:48 +00:00
|
|
|
AND {visibility_filter}
|
2022-01-07 16:33:23 +00:00
|
|
|
AND ($max_post_id::uuid IS NULL OR post.id < $max_post_id)
|
2021-12-01 21:55:38 +00:00
|
|
|
ORDER BY post.id DESC
|
2022-01-07 16:33:23 +00:00
|
|
|
LIMIT $limit
|
2021-12-01 21:58:18 +00:00
|
|
|
",
|
|
|
|
related_attachments=RELATED_ATTACHMENTS,
|
|
|
|
related_mentions=RELATED_MENTIONS,
|
2021-12-07 23:28:58 +00:00
|
|
|
related_tags=RELATED_TAGS,
|
2022-01-08 11:20:48 +00:00
|
|
|
visibility_filter=build_visibility_filter(),
|
2021-12-01 21:58:18 +00:00
|
|
|
);
|
2022-01-07 16:33:23 +00:00
|
|
|
let query = query!(
|
|
|
|
&statement,
|
|
|
|
current_user_id=current_user_id,
|
|
|
|
max_post_id=max_post_id,
|
|
|
|
limit=limit,
|
|
|
|
)?;
|
|
|
|
let rows = db_client.query(query.sql(), query.parameters()).await?;
|
2021-12-01 21:58:18 +00:00
|
|
|
let posts: Vec<Post> = rows.iter()
|
|
|
|
.map(Post::try_from)
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(posts)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_posts(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
posts_ids: Vec<Uuid>,
|
|
|
|
) -> Result<Vec<Post>, DatabaseError> {
|
|
|
|
let statement = format!(
|
|
|
|
"
|
|
|
|
SELECT
|
|
|
|
post, actor_profile,
|
|
|
|
{related_attachments},
|
2021-12-07 23:28:58 +00:00
|
|
|
{related_mentions},
|
|
|
|
{related_tags}
|
2021-12-01 21:58:18 +00:00
|
|
|
FROM post
|
|
|
|
JOIN actor_profile ON post.author_id = actor_profile.id
|
|
|
|
WHERE post.id = ANY($1)
|
|
|
|
",
|
|
|
|
related_attachments=RELATED_ATTACHMENTS,
|
|
|
|
related_mentions=RELATED_MENTIONS,
|
2021-12-07 23:28:58 +00:00
|
|
|
related_tags=RELATED_TAGS,
|
2021-12-01 21:58:18 +00:00
|
|
|
);
|
|
|
|
let rows = db_client.query(
|
|
|
|
statement.as_str(),
|
|
|
|
&[&posts_ids],
|
|
|
|
).await?;
|
|
|
|
let posts: Vec<Post> = rows.iter()
|
|
|
|
.map(Post::try_from)
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(posts)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_posts_by_author(
|
|
|
|
db_client: &impl GenericClient,
|
2022-01-07 01:10:41 +00:00
|
|
|
profile_id: &Uuid,
|
|
|
|
current_user_id: Option<&Uuid>,
|
2021-12-01 21:58:18 +00:00
|
|
|
include_replies: bool,
|
2021-12-14 16:16:30 +00:00
|
|
|
max_post_id: Option<Uuid>,
|
|
|
|
limit: i64,
|
2021-12-01 21:58:18 +00:00
|
|
|
) -> Result<Vec<Post>, DatabaseError> {
|
2022-01-08 11:20:48 +00:00
|
|
|
let mut condition = format!(
|
|
|
|
"post.author_id = $profile_id
|
|
|
|
AND {visibility_filter}
|
|
|
|
AND ($max_post_id::uuid IS NULL OR post.id < $max_post_id)",
|
|
|
|
visibility_filter=build_visibility_filter(),
|
|
|
|
);
|
2021-12-01 21:58:18 +00:00
|
|
|
if !include_replies {
|
|
|
|
condition.push_str(" AND post.in_reply_to_id IS NULL");
|
|
|
|
};
|
|
|
|
let statement = format!(
|
|
|
|
"
|
|
|
|
SELECT
|
|
|
|
post, actor_profile,
|
|
|
|
{related_attachments},
|
2021-12-07 23:28:58 +00:00
|
|
|
{related_mentions},
|
|
|
|
{related_tags}
|
2021-12-01 21:58:18 +00:00
|
|
|
FROM post
|
|
|
|
JOIN actor_profile ON post.author_id = actor_profile.id
|
|
|
|
WHERE {condition}
|
|
|
|
ORDER BY post.created_at DESC
|
2022-01-07 16:33:23 +00:00
|
|
|
LIMIT $limit
|
2021-12-01 21:58:18 +00:00
|
|
|
",
|
|
|
|
related_attachments=RELATED_ATTACHMENTS,
|
|
|
|
related_mentions=RELATED_MENTIONS,
|
2021-12-07 23:28:58 +00:00
|
|
|
related_tags=RELATED_TAGS,
|
2021-12-01 21:58:18 +00:00
|
|
|
condition=condition,
|
|
|
|
);
|
2022-01-07 16:33:23 +00:00
|
|
|
let query = query!(
|
|
|
|
&statement,
|
|
|
|
profile_id=profile_id,
|
|
|
|
current_user_id=current_user_id,
|
|
|
|
max_post_id=max_post_id,
|
|
|
|
limit=limit,
|
|
|
|
)?;
|
|
|
|
let rows = db_client.query(query.sql(), query.parameters()).await?;
|
2021-12-01 21:58:18 +00:00
|
|
|
let posts: Vec<Post> = rows.iter()
|
|
|
|
.map(Post::try_from)
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(posts)
|
|
|
|
}
|
|
|
|
|
2021-12-12 13:38:10 +00:00
|
|
|
pub async fn get_posts_by_tag(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
tag_name: &str,
|
2022-01-07 01:10:41 +00:00
|
|
|
current_user_id: Option<&Uuid>,
|
2021-12-12 13:38:10 +00:00
|
|
|
max_post_id: Option<Uuid>,
|
|
|
|
limit: i64,
|
|
|
|
) -> Result<Vec<Post>, DatabaseError> {
|
2022-01-07 16:33:23 +00:00
|
|
|
let tag_name = tag_name.to_lowercase();
|
2021-12-12 13:38:10 +00:00
|
|
|
let statement = format!(
|
|
|
|
"
|
|
|
|
SELECT
|
|
|
|
post, actor_profile,
|
|
|
|
{related_attachments},
|
|
|
|
{related_mentions},
|
|
|
|
{related_tags}
|
|
|
|
FROM post
|
|
|
|
JOIN actor_profile ON post.author_id = actor_profile.id
|
|
|
|
WHERE
|
2022-01-08 11:20:48 +00:00
|
|
|
EXISTS (
|
2021-12-12 13:38:10 +00:00
|
|
|
SELECT 1 FROM post_tag JOIN tag ON post_tag.tag_id = tag.id
|
2022-01-07 16:33:23 +00:00
|
|
|
WHERE post_tag.post_id = post.id AND tag.tag_name = $tag_name
|
2021-12-12 13:38:10 +00:00
|
|
|
)
|
2022-01-08 11:20:48 +00:00
|
|
|
AND {visibility_filter}
|
2022-01-07 16:33:23 +00:00
|
|
|
AND ($max_post_id::uuid IS NULL OR post.id < $max_post_id)
|
2021-12-12 13:38:10 +00:00
|
|
|
ORDER BY post.id DESC
|
2022-01-07 16:33:23 +00:00
|
|
|
LIMIT $limit
|
2021-12-12 13:38:10 +00:00
|
|
|
",
|
|
|
|
related_attachments=RELATED_ATTACHMENTS,
|
|
|
|
related_mentions=RELATED_MENTIONS,
|
|
|
|
related_tags=RELATED_TAGS,
|
2022-01-08 11:20:48 +00:00
|
|
|
visibility_filter=build_visibility_filter(),
|
2021-12-12 13:38:10 +00:00
|
|
|
);
|
2022-01-07 16:33:23 +00:00
|
|
|
let query = query!(
|
|
|
|
&statement,
|
|
|
|
tag_name=tag_name,
|
|
|
|
current_user_id=current_user_id,
|
|
|
|
max_post_id=max_post_id,
|
|
|
|
limit=limit,
|
|
|
|
)?;
|
|
|
|
let rows = db_client.query(query.sql(), query.parameters()).await?;
|
2021-12-12 13:38:10 +00:00
|
|
|
let posts: Vec<Post> = rows.iter()
|
|
|
|
.map(Post::try_from)
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(posts)
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
pub async fn get_post_by_id(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
post_id: &Uuid,
|
|
|
|
) -> Result<Post, DatabaseError> {
|
2021-11-11 18:35:27 +00:00
|
|
|
let statement = format!(
|
2021-04-09 00:22:17 +00:00
|
|
|
"
|
|
|
|
SELECT
|
|
|
|
post, actor_profile,
|
2021-11-11 19:10:28 +00:00
|
|
|
{related_attachments},
|
2021-12-07 23:28:58 +00:00
|
|
|
{related_mentions},
|
|
|
|
{related_tags}
|
2021-04-09 00:22:17 +00:00
|
|
|
FROM post
|
|
|
|
JOIN actor_profile ON post.author_id = actor_profile.id
|
|
|
|
WHERE post.id = $1
|
|
|
|
",
|
2021-11-11 18:35:27 +00:00
|
|
|
related_attachments=RELATED_ATTACHMENTS,
|
2021-11-11 19:10:28 +00:00
|
|
|
related_mentions=RELATED_MENTIONS,
|
2021-12-07 23:28:58 +00:00
|
|
|
related_tags=RELATED_TAGS,
|
2021-11-11 18:35:27 +00:00
|
|
|
);
|
|
|
|
let maybe_row = db_client.query_opt(
|
|
|
|
statement.as_str(),
|
2021-04-09 00:22:17 +00:00
|
|
|
&[&post_id],
|
|
|
|
).await?;
|
|
|
|
let post = match maybe_row {
|
|
|
|
Some(row) => Post::try_from(&row)?,
|
|
|
|
None => return Err(DatabaseError::NotFound("post")),
|
|
|
|
};
|
|
|
|
Ok(post)
|
|
|
|
}
|
|
|
|
|
2021-10-08 13:56:45 +00:00
|
|
|
/// Given a post ID, finds all items in thread.
|
|
|
|
/// Results are sorted by tree path.
|
2021-09-22 16:32:44 +00:00
|
|
|
pub async fn get_thread(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
post_id: &Uuid,
|
2021-11-19 23:17:04 +00:00
|
|
|
current_user_id: Option<&Uuid>,
|
2021-09-22 16:32:44 +00:00
|
|
|
) -> Result<Vec<Post>, DatabaseError> {
|
|
|
|
// TODO: limit recursion depth
|
2021-11-11 18:35:27 +00:00
|
|
|
let statement = format!(
|
2021-09-22 16:32:44 +00:00
|
|
|
"
|
|
|
|
WITH RECURSIVE
|
|
|
|
ancestors (id, in_reply_to_id) AS (
|
|
|
|
SELECT post.id, post.in_reply_to_id FROM post
|
2022-01-08 11:20:48 +00:00
|
|
|
WHERE post.id = $post_id AND {visibility_filter}
|
2021-09-22 16:32:44 +00:00
|
|
|
UNION ALL
|
|
|
|
SELECT post.id, post.in_reply_to_id FROM post
|
|
|
|
JOIN ancestors ON post.id = ancestors.in_reply_to_id
|
|
|
|
),
|
|
|
|
context (id, path) AS (
|
|
|
|
SELECT ancestors.id, ARRAY[ancestors.id] FROM ancestors
|
|
|
|
WHERE ancestors.in_reply_to_id IS NULL
|
|
|
|
UNION
|
|
|
|
SELECT post.id, array_append(context.path, post.id) FROM post
|
|
|
|
JOIN context ON post.in_reply_to_id = context.id
|
|
|
|
)
|
|
|
|
SELECT
|
|
|
|
post, actor_profile,
|
2021-11-11 19:10:28 +00:00
|
|
|
{related_attachments},
|
2021-12-07 23:28:58 +00:00
|
|
|
{related_mentions},
|
|
|
|
{related_tags}
|
2021-09-22 16:32:44 +00:00
|
|
|
FROM post
|
|
|
|
JOIN context ON post.id = context.id
|
|
|
|
JOIN actor_profile ON post.author_id = actor_profile.id
|
2022-01-08 11:20:48 +00:00
|
|
|
WHERE {visibility_filter}
|
2021-09-22 16:32:44 +00:00
|
|
|
ORDER BY context.path
|
|
|
|
",
|
2021-11-11 18:35:27 +00:00
|
|
|
related_attachments=RELATED_ATTACHMENTS,
|
2021-11-11 19:10:28 +00:00
|
|
|
related_mentions=RELATED_MENTIONS,
|
2021-12-07 23:28:58 +00:00
|
|
|
related_tags=RELATED_TAGS,
|
2022-01-08 11:20:48 +00:00
|
|
|
visibility_filter=build_visibility_filter(),
|
2021-11-11 18:35:27 +00:00
|
|
|
);
|
2022-01-07 16:33:23 +00:00
|
|
|
let query = query!(
|
|
|
|
&statement,
|
|
|
|
post_id=post_id,
|
|
|
|
current_user_id=current_user_id,
|
|
|
|
)?;
|
|
|
|
let rows = db_client.query(query.sql(), query.parameters()).await?;
|
2021-09-22 16:32:44 +00:00
|
|
|
let posts: Vec<Post> = rows.iter()
|
2021-11-13 17:37:31 +00:00
|
|
|
.map(Post::try_from)
|
2021-09-22 16:32:44 +00:00
|
|
|
.collect::<Result<_, _>>()?;
|
2021-11-13 17:37:31 +00:00
|
|
|
if posts.is_empty() {
|
2021-10-08 12:56:02 +00:00
|
|
|
return Err(DatabaseError::NotFound("post"));
|
|
|
|
}
|
2021-09-22 16:32:44 +00:00
|
|
|
Ok(posts)
|
|
|
|
}
|
|
|
|
|
2021-10-09 23:59:45 +00:00
|
|
|
pub async fn get_post_by_object_id(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
object_id: &str,
|
|
|
|
) -> Result<Post, DatabaseError> {
|
2021-11-11 18:35:27 +00:00
|
|
|
let statement = format!(
|
2021-10-09 23:59:45 +00:00
|
|
|
"
|
|
|
|
SELECT
|
|
|
|
post, actor_profile,
|
2021-11-11 19:10:28 +00:00
|
|
|
{related_attachments},
|
2021-12-07 23:28:58 +00:00
|
|
|
{related_mentions},
|
|
|
|
{related_tags}
|
2021-10-09 23:59:45 +00:00
|
|
|
FROM post
|
|
|
|
JOIN actor_profile ON post.author_id = actor_profile.id
|
|
|
|
WHERE post.object_id = $1
|
|
|
|
",
|
2021-11-11 18:35:27 +00:00
|
|
|
related_attachments=RELATED_ATTACHMENTS,
|
2021-11-11 19:10:28 +00:00
|
|
|
related_mentions=RELATED_MENTIONS,
|
2021-12-07 23:28:58 +00:00
|
|
|
related_tags=RELATED_TAGS,
|
2021-11-11 18:35:27 +00:00
|
|
|
);
|
|
|
|
let maybe_row = db_client.query_opt(
|
|
|
|
statement.as_str(),
|
2021-10-09 23:59:45 +00:00
|
|
|
&[&object_id],
|
|
|
|
).await?;
|
|
|
|
let row = maybe_row.ok_or(DatabaseError::NotFound("post"))?;
|
|
|
|
let post = Post::try_from(&row)?;
|
|
|
|
Ok(post)
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
pub async fn get_post_by_ipfs_cid(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
ipfs_cid: &str,
|
|
|
|
) -> Result<Post, DatabaseError> {
|
2021-11-11 18:35:27 +00:00
|
|
|
let statement = format!(
|
2021-04-09 00:22:17 +00:00
|
|
|
"
|
|
|
|
SELECT
|
|
|
|
post, actor_profile,
|
2021-11-11 19:10:28 +00:00
|
|
|
{related_attachments},
|
2021-12-07 23:28:58 +00:00
|
|
|
{related_mentions},
|
|
|
|
{related_tags}
|
2021-04-09 00:22:17 +00:00
|
|
|
FROM post
|
|
|
|
JOIN actor_profile ON post.author_id = actor_profile.id
|
|
|
|
WHERE post.ipfs_cid = $1
|
|
|
|
",
|
2021-11-11 18:35:27 +00:00
|
|
|
related_attachments=RELATED_ATTACHMENTS,
|
2021-11-11 19:10:28 +00:00
|
|
|
related_mentions=RELATED_MENTIONS,
|
2021-12-07 23:28:58 +00:00
|
|
|
related_tags=RELATED_TAGS,
|
2021-11-11 18:35:27 +00:00
|
|
|
);
|
|
|
|
let result = db_client.query_opt(
|
|
|
|
statement.as_str(),
|
2021-04-09 00:22:17 +00:00
|
|
|
&[&ipfs_cid],
|
|
|
|
).await?;
|
|
|
|
let post = match result {
|
|
|
|
Some(row) => Post::try_from(&row)?,
|
|
|
|
None => return Err(DatabaseError::NotFound("post")),
|
|
|
|
};
|
|
|
|
Ok(post)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn update_post(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
post: &Post,
|
|
|
|
) -> Result<(), DatabaseError> {
|
|
|
|
let updated_count = db_client.execute(
|
|
|
|
"
|
|
|
|
UPDATE post
|
|
|
|
SET
|
|
|
|
content = $1,
|
|
|
|
ipfs_cid = $2,
|
|
|
|
token_id = $3,
|
|
|
|
token_tx_id = $4
|
|
|
|
WHERE id = $5
|
|
|
|
",
|
|
|
|
&[
|
|
|
|
&post.content,
|
|
|
|
&post.ipfs_cid,
|
|
|
|
&post.token_id,
|
|
|
|
&post.token_tx_id,
|
|
|
|
&post.id,
|
|
|
|
],
|
|
|
|
).await?;
|
|
|
|
if updated_count == 0 {
|
|
|
|
return Err(DatabaseError::NotFound("post"));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-09-24 14:02:56 +00:00
|
|
|
pub async fn update_reply_count(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
post_id: &Uuid,
|
|
|
|
change: i32,
|
|
|
|
) -> Result<(), DatabaseError> {
|
|
|
|
let updated_count = db_client.execute(
|
|
|
|
"
|
|
|
|
UPDATE post
|
|
|
|
SET reply_count = reply_count + $1
|
|
|
|
WHERE id = $2
|
|
|
|
",
|
|
|
|
&[&change, &post_id],
|
|
|
|
).await?;
|
|
|
|
if updated_count == 0 {
|
|
|
|
return Err(DatabaseError::NotFound("post"));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-17 16:55:33 +00:00
|
|
|
pub async fn update_reaction_count(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
post_id: &Uuid,
|
|
|
|
change: i32,
|
|
|
|
) -> Result<(), DatabaseError> {
|
|
|
|
let updated_count = db_client.execute(
|
|
|
|
"
|
|
|
|
UPDATE post
|
|
|
|
SET reaction_count = reaction_count + $1
|
|
|
|
WHERE id = $2
|
|
|
|
",
|
|
|
|
&[&change, &post_id],
|
|
|
|
).await?;
|
|
|
|
if updated_count == 0 {
|
|
|
|
return Err(DatabaseError::NotFound("post"));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-11-24 16:39:30 +00:00
|
|
|
pub async fn update_repost_count(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
post_id: &Uuid,
|
|
|
|
change: i32,
|
|
|
|
) -> Result<(), DatabaseError> {
|
|
|
|
let updated_count = db_client.execute(
|
|
|
|
"
|
|
|
|
UPDATE post
|
|
|
|
SET repost_count = repost_count + $1
|
|
|
|
WHERE id = $2
|
|
|
|
",
|
|
|
|
&[&change, &post_id],
|
|
|
|
).await?;
|
|
|
|
if updated_count == 0 {
|
|
|
|
return Err(DatabaseError::NotFound("post"));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-12-04 22:48:00 +00:00
|
|
|
pub async fn get_post_author(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
post_id: &Uuid,
|
|
|
|
) -> Result<DbActorProfile, DatabaseError> {
|
|
|
|
let maybe_row = db_client.query_opt(
|
|
|
|
"
|
|
|
|
SELECT actor_profile
|
|
|
|
FROM post
|
|
|
|
JOIN actor_profile ON post.author_id = actor_profile.id
|
|
|
|
WHERE post.id = $1
|
|
|
|
",
|
|
|
|
&[&post_id],
|
|
|
|
).await?;
|
|
|
|
let row = maybe_row.ok_or(DatabaseError::NotFound("post"))?;
|
|
|
|
let author: DbActorProfile = row.try_get("actor_profile")?;
|
|
|
|
Ok(author)
|
|
|
|
}
|
|
|
|
|
2021-11-24 16:39:30 +00:00
|
|
|
/// Finds reposts of given posts and returns their IDs
|
|
|
|
pub async fn find_reposts_by_user(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
user_id: &Uuid,
|
|
|
|
posts_ids: &[Uuid],
|
|
|
|
) -> Result<Vec<Uuid>, DatabaseError> {
|
|
|
|
let rows = db_client.query(
|
|
|
|
"
|
|
|
|
SELECT post.id
|
|
|
|
FROM post
|
|
|
|
WHERE post.author_id = $1 AND post.repost_of_id = ANY($2)
|
|
|
|
",
|
|
|
|
&[&user_id, &posts_ids],
|
|
|
|
).await?;
|
|
|
|
let reposts: Vec<Uuid> = rows.iter()
|
|
|
|
.map(|row| row.try_get("id"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(reposts)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Finds items reposted by user among given posts
|
|
|
|
pub async fn find_reposted_by_user(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
user_id: &Uuid,
|
|
|
|
posts_ids: &[Uuid],
|
|
|
|
) -> Result<Vec<Uuid>, DatabaseError> {
|
|
|
|
let rows = db_client.query(
|
|
|
|
"
|
|
|
|
SELECT post.id
|
|
|
|
FROM post
|
|
|
|
WHERE post.id = ANY($2) AND EXISTS (
|
|
|
|
SELECT 1 FROM post AS repost
|
|
|
|
WHERE repost.author_id = $1 AND repost.repost_of_id = post.id
|
|
|
|
)
|
|
|
|
",
|
|
|
|
&[&user_id, &posts_ids],
|
|
|
|
).await?;
|
|
|
|
let reposted: Vec<Uuid> = rows.iter()
|
|
|
|
.map(|row| row.try_get("id"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(reposted)
|
|
|
|
}
|
|
|
|
|
2021-09-29 01:03:47 +00:00
|
|
|
pub async fn get_token_waitlist(
|
2021-04-09 00:22:17 +00:00
|
|
|
db_client: &impl GenericClient,
|
2021-09-29 01:03:47 +00:00
|
|
|
) -> Result<Vec<Uuid>, DatabaseError> {
|
|
|
|
let rows = db_client.query(
|
2021-04-09 00:22:17 +00:00
|
|
|
"
|
2021-09-29 01:03:47 +00:00
|
|
|
SELECT post.id
|
2021-04-09 00:22:17 +00:00
|
|
|
FROM post
|
2021-12-03 18:52:34 +00:00
|
|
|
WHERE token_tx_id IS NOT NULL AND token_id IS NULL
|
2021-04-09 00:22:17 +00:00
|
|
|
",
|
|
|
|
&[],
|
|
|
|
).await?;
|
2021-09-29 01:03:47 +00:00
|
|
|
let waitlist: Vec<Uuid> = rows.iter()
|
|
|
|
.map(|row| row.try_get("id"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(waitlist)
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|
2021-09-25 20:46:19 +00:00
|
|
|
|
2021-09-29 11:43:45 +00:00
|
|
|
/// Deletes post from database and returns collection of orphaned objects.
|
2021-09-25 20:46:19 +00:00
|
|
|
pub async fn delete_post(
|
|
|
|
db_client: &mut impl GenericClient,
|
|
|
|
post_id: &Uuid,
|
2021-09-29 11:43:45 +00:00
|
|
|
) -> Result<DeletionQueue, DatabaseError> {
|
2021-09-25 20:46:19 +00:00
|
|
|
let transaction = db_client.transaction().await?;
|
|
|
|
// Get list of attached files
|
2021-09-25 21:51:28 +00:00
|
|
|
let files_rows = transaction.query(
|
2021-09-25 20:46:19 +00:00
|
|
|
"
|
|
|
|
SELECT file_name
|
|
|
|
FROM media_attachment WHERE post_id = $1
|
|
|
|
",
|
|
|
|
&[&post_id],
|
|
|
|
).await?;
|
2021-09-25 21:51:28 +00:00
|
|
|
let files: Vec<String> = files_rows.iter()
|
2021-09-25 20:46:19 +00:00
|
|
|
.map(|row| row.try_get("file_name"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
2021-09-29 11:43:45 +00:00
|
|
|
// Get list of linked IPFS objects
|
|
|
|
let ipfs_objects_rows = transaction.query(
|
|
|
|
"
|
|
|
|
SELECT ipfs_cid
|
|
|
|
FROM media_attachment
|
|
|
|
WHERE post_id = $1 AND ipfs_cid IS NOT NULL
|
|
|
|
UNION ALL
|
|
|
|
SELECT ipfs_cid
|
|
|
|
FROM post
|
|
|
|
WHERE id = $1 AND ipfs_cid IS NOT NULL
|
|
|
|
",
|
|
|
|
&[&post_id],
|
|
|
|
).await?;
|
|
|
|
let ipfs_objects: Vec<String> = ipfs_objects_rows.iter()
|
|
|
|
.map(|row| row.try_get("ipfs_cid"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
2021-09-25 20:46:19 +00:00
|
|
|
// Delete post
|
|
|
|
let maybe_post_row = transaction.query_opt(
|
|
|
|
"
|
|
|
|
DELETE FROM post WHERE id = $1
|
|
|
|
RETURNING post
|
|
|
|
",
|
|
|
|
&[&post_id],
|
|
|
|
).await?;
|
|
|
|
let post_row = maybe_post_row.ok_or(DatabaseError::NotFound("post"))?;
|
|
|
|
let db_post: DbPost = post_row.try_get("post")?;
|
|
|
|
// Update counters
|
|
|
|
if let Some(parent_id) = &db_post.in_reply_to_id {
|
|
|
|
update_reply_count(&transaction, parent_id, -1).await?;
|
|
|
|
}
|
2021-11-24 16:39:30 +00:00
|
|
|
if let Some(repost_of_id) = &db_post.repost_of_id {
|
|
|
|
update_repost_count(&transaction, repost_of_id, -1).await?;
|
|
|
|
};
|
2021-09-25 20:46:19 +00:00
|
|
|
update_post_count(&transaction, &db_post.author_id, -1).await?;
|
|
|
|
let orphaned_files = find_orphaned_files(&transaction, files).await?;
|
2021-09-29 11:43:45 +00:00
|
|
|
let orphaned_ipfs_objects = find_orphaned_ipfs_objects(&transaction, ipfs_objects).await?;
|
2021-09-25 20:46:19 +00:00
|
|
|
transaction.commit().await?;
|
2021-09-29 11:43:45 +00:00
|
|
|
Ok(DeletionQueue {
|
|
|
|
files: orphaned_files,
|
|
|
|
ipfs_objects: orphaned_ipfs_objects,
|
|
|
|
})
|
2021-09-25 20:46:19 +00:00
|
|
|
}
|
2022-01-08 11:20:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use serial_test::serial;
|
|
|
|
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;
|
|
|
|
use crate::models::users::queries::create_user;
|
|
|
|
use crate::models::users::types::UserCreateData;
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
|
|
|
async fn test_create_post() {
|
|
|
|
let db_client = &mut create_test_database().await;
|
|
|
|
let profile_data = ProfileCreateData {
|
|
|
|
username: "test".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let profile = create_profile(db_client, profile_data).await.unwrap();
|
|
|
|
let post_data = PostCreateData {
|
|
|
|
content: "test post".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let post = create_post(db_client, &profile.id, post_data).await.unwrap();
|
|
|
|
assert_eq!(post.content, "test post");
|
|
|
|
assert_eq!(post.author.id, profile.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
|
|
|
async fn test_home_timeline() {
|
|
|
|
let db_client = &mut create_test_database().await;
|
|
|
|
let current_user_data = UserCreateData {
|
|
|
|
username: "test".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let current_user = create_user(db_client, current_user_data).await.unwrap();
|
|
|
|
// Current user's post
|
|
|
|
let post_data_1 = PostCreateData {
|
|
|
|
content: "my post".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let post_1 = create_post(db_client, ¤t_user.id, post_data_1).await.unwrap();
|
|
|
|
// Current user's direct message
|
|
|
|
let post_data_2 = PostCreateData {
|
|
|
|
content: "my post".to_string(),
|
|
|
|
visibility: Visibility::Direct,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let post_2 = create_post(db_client, ¤t_user.id, post_data_2).await.unwrap();
|
2022-01-05 18:47:02 +00:00
|
|
|
// Another user's public post
|
2022-01-08 11:20:48 +00:00
|
|
|
let user_data_1 = UserCreateData {
|
|
|
|
username: "another-user".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let user_1 = create_user(db_client, user_data_1).await.unwrap();
|
|
|
|
let post_data_3 = PostCreateData {
|
|
|
|
content: "test post".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let post_3 = create_post(db_client, &user_1.id, post_data_3).await.unwrap();
|
|
|
|
// Direct message from another user to current user
|
|
|
|
let post_data_4 = PostCreateData {
|
|
|
|
content: "test post".to_string(),
|
|
|
|
visibility: Visibility::Direct,
|
|
|
|
mentions: vec![current_user.id],
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let post_4 = create_post(db_client, &user_1.id, post_data_4).await.unwrap();
|
2022-01-05 18:47:02 +00:00
|
|
|
// Followers-only post from another user
|
|
|
|
let post_data_5 = PostCreateData {
|
|
|
|
content: "followers only".to_string(),
|
|
|
|
visibility: Visibility::Followers,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let post_5 = create_post(db_client, &user_1.id, post_data_5).await.unwrap();
|
|
|
|
// Followed user's public post
|
2022-01-08 11:20:48 +00:00
|
|
|
let user_data_2 = UserCreateData {
|
|
|
|
username: "followed".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let user_2 = create_user(db_client, user_data_2).await.unwrap();
|
|
|
|
follow(db_client, ¤t_user.id, &user_2.id).await.unwrap();
|
2022-01-05 18:47:02 +00:00
|
|
|
let post_data_6 = PostCreateData {
|
2022-01-08 11:20:48 +00:00
|
|
|
content: "test post".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
2022-01-05 18:47:02 +00:00
|
|
|
let post_6 = create_post(db_client, &user_2.id, post_data_6).await.unwrap();
|
2022-01-08 11:20:48 +00:00
|
|
|
// Direct message from followed user sent to another user
|
2022-01-05 18:47:02 +00:00
|
|
|
let post_data_7 = PostCreateData {
|
2022-01-08 11:20:48 +00:00
|
|
|
content: "test post".to_string(),
|
|
|
|
visibility: Visibility::Direct,
|
|
|
|
mentions: vec![user_1.id],
|
|
|
|
..Default::default()
|
|
|
|
};
|
2022-01-05 18:47:02 +00:00
|
|
|
let post_7 = create_post(db_client, &user_2.id, post_data_7).await.unwrap();
|
|
|
|
// Followers-only post from followed user
|
|
|
|
let post_data_8 = 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();
|
2022-01-08 11:20:48 +00:00
|
|
|
|
|
|
|
let timeline = get_home_timeline(db_client, ¤t_user.id, None, 10).await.unwrap();
|
2022-01-05 18:47:02 +00:00
|
|
|
assert_eq!(timeline.len(), 5);
|
2022-01-08 11:20:48 +00:00
|
|
|
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);
|
2022-01-05 18:47:02 +00:00
|
|
|
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);
|
2022-01-08 11:20:48 +00:00
|
|
|
}
|
2022-01-09 12:23:15 +00:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
|
|
|
async fn test_profile_timeline_public() {
|
|
|
|
let db_client = &mut create_test_database().await;
|
|
|
|
let user_data = UserCreateData {
|
|
|
|
username: "test".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let user = create_user(db_client, user_data).await.unwrap();
|
|
|
|
// Public post
|
|
|
|
let post_data_1 = PostCreateData {
|
|
|
|
content: "my post".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let post_1 = create_post(db_client, &user.id, post_data_1).await.unwrap();
|
|
|
|
// Direct message
|
|
|
|
let post_data_2 = PostCreateData {
|
|
|
|
content: "my post".to_string(),
|
|
|
|
visibility: Visibility::Direct,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let post_2 = create_post(db_client, &user.id, post_data_2).await.unwrap();
|
|
|
|
|
|
|
|
// Anonymous viewer
|
|
|
|
let timeline = get_posts_by_author(
|
|
|
|
db_client, &user.id, None, false, None, 10
|
|
|
|
).await.unwrap();
|
|
|
|
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_2.id), false);
|
|
|
|
}
|
2022-01-08 11:20:48 +00:00
|
|
|
}
|