2021-04-09 00:22:17 +00:00
|
|
|
use std::convert::TryFrom;
|
|
|
|
|
|
|
|
use chrono::Utc;
|
2021-11-19 23:17:04 +00:00
|
|
|
use postgres_types::ToSql;
|
2021-04-09 00:22:17 +00:00
|
|
|
use tokio_postgres::GenericClient;
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
2021-11-24 16:39:30 +00:00
|
|
|
use crate::database::catch_unique_violation;
|
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-10-15 00:27:39 +00:00
|
|
|
use crate::models::notifications::queries::create_reply_notification;
|
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;
|
2021-11-19 00:32:22 +00:00
|
|
|
use super::types::{DbPost, Post, PostCreateData, Visibility};
|
2021-04-09 00:22:17 +00:00
|
|
|
|
2021-11-11 18:35:27 +00:00
|
|
|
pub const RELATED_ATTACHMENTS: &str =
|
|
|
|
"ARRAY(
|
|
|
|
SELECT media_attachment
|
|
|
|
FROM media_attachment WHERE post_id = post.id
|
|
|
|
) AS attachments";
|
|
|
|
|
2021-11-11 19:10:28 +00:00
|
|
|
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-11-07 08:52:57 +00:00
|
|
|
pub async fn get_home_timeline(
|
2021-04-09 00:22:17 +00:00
|
|
|
db_client: &impl GenericClient,
|
|
|
|
current_user_id: &Uuid,
|
|
|
|
) -> Result<Vec<Post>, DatabaseError> {
|
2021-09-22 16:32:44 +00:00
|
|
|
// Select posts from follows + own posts.
|
2021-11-19 18:22:24 +00:00
|
|
|
// Exclude direct messages where current user is not mentioned.
|
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},
|
|
|
|
{related_mentions}
|
2021-04-09 00:22:17 +00:00
|
|
|
FROM post
|
|
|
|
JOIN actor_profile ON post.author_id = actor_profile.id
|
|
|
|
WHERE
|
2021-11-19 18:22:24 +00:00
|
|
|
(
|
2021-09-22 16:32:44 +00:00
|
|
|
post.author_id = $1
|
|
|
|
OR EXISTS (
|
|
|
|
SELECT 1 FROM relationship
|
|
|
|
WHERE source_id = $1 AND target_id = post.author_id
|
|
|
|
)
|
2021-04-09 00:22:17 +00:00
|
|
|
)
|
2021-11-19 18:22:24 +00:00
|
|
|
AND (
|
|
|
|
post.visibility = {visibility_public}
|
|
|
|
OR EXISTS (
|
|
|
|
SELECT 1 FROM mention
|
|
|
|
WHERE post_id = post.id AND profile_id = $1
|
|
|
|
)
|
|
|
|
)
|
2021-04-09 00:22:17 +00:00
|
|
|
ORDER BY post.created_at DESC
|
|
|
|
",
|
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-11-19 18:22:24 +00:00
|
|
|
visibility_public=i16::from(&Visibility::Public),
|
2021-11-11 18:35:27 +00:00
|
|
|
);
|
|
|
|
let rows = db_client.query(
|
|
|
|
statement.as_str(),
|
2021-04-09 00:22:17 +00:00
|
|
|
&[¤t_user_id],
|
|
|
|
).await?;
|
|
|
|
let posts: Vec<Post> = rows.iter()
|
2021-11-13 17:37:31 +00:00
|
|
|
.map(Post::try_from)
|
2021-04-09 00:22:17 +00:00
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(posts)
|
|
|
|
}
|
|
|
|
|
2021-11-24 16:39:30 +00:00
|
|
|
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},
|
|
|
|
{related_mentions}
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
pub async fn get_posts_by_author(
|
|
|
|
db_client: &impl GenericClient,
|
|
|
|
account_id: &Uuid,
|
2021-11-01 20:49:36 +00:00
|
|
|
include_replies: bool,
|
2021-11-19 00:32:22 +00:00
|
|
|
include_private: bool,
|
2021-04-09 00:22:17 +00:00
|
|
|
) -> Result<Vec<Post>, DatabaseError> {
|
2021-11-19 00:32:22 +00:00
|
|
|
let mut condition = "post.author_id = $1".to_string();
|
|
|
|
if !include_replies {
|
|
|
|
condition.push_str(" AND post.in_reply_to_id IS NULL");
|
|
|
|
};
|
|
|
|
if !include_private {
|
|
|
|
let only_public = format!(
|
|
|
|
" AND visibility = {}",
|
|
|
|
i16::from(&Visibility::Public),
|
|
|
|
);
|
|
|
|
condition.push_str(&only_public);
|
2021-11-01 20:49:36 +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},
|
|
|
|
{related_mentions}
|
2021-04-09 00:22:17 +00:00
|
|
|
FROM post
|
|
|
|
JOIN actor_profile ON post.author_id = actor_profile.id
|
2021-11-01 20:49:36 +00:00
|
|
|
WHERE {condition}
|
2021-04-09 00:22:17 +00:00
|
|
|
ORDER BY post.created_at DESC
|
|
|
|
",
|
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-11-01 20:49:36 +00:00
|
|
|
condition=condition,
|
|
|
|
);
|
|
|
|
let rows = db_client.query(
|
|
|
|
statement.as_str(),
|
2021-04-09 00:22:17 +00:00
|
|
|
&[&account_id],
|
|
|
|
).await?;
|
|
|
|
let posts: Vec<Post> = rows.iter()
|
2021-11-13 17:37:31 +00:00
|
|
|
.map(Post::try_from)
|
2021-04-09 00:22:17 +00:00
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(posts)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn create_post(
|
|
|
|
db_client: &mut impl GenericClient,
|
|
|
|
author_id: &Uuid,
|
|
|
|
data: PostCreateData,
|
|
|
|
) -> Result<Post, DatabaseError> {
|
|
|
|
let transaction = db_client.transaction().await?;
|
|
|
|
let post_id = uuid::Uuid::new_v4();
|
|
|
|
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-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-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-10-28 23:26:31 +00:00
|
|
|
let in_reply_to = get_post_by_id(&transaction, in_reply_to_id).await?;
|
2021-11-21 14:38:27 +00:00
|
|
|
if in_reply_to.author.is_local() &&
|
|
|
|
in_reply_to.author.id != db_post.author_id
|
|
|
|
{
|
2021-10-28 23:26:31 +00:00
|
|
|
create_reply_notification(
|
|
|
|
&transaction,
|
|
|
|
&db_post.author_id,
|
|
|
|
&in_reply_to.author.id,
|
|
|
|
&db_post.id,
|
|
|
|
).await?;
|
|
|
|
}
|
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-09-24 14:02:56 +00:00
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
transaction.commit().await?;
|
2021-11-21 15:19:50 +00:00
|
|
|
let post = Post::new(db_post, author, db_attachments, db_mentions)?;
|
2021-04-09 00:22:17 +00:00
|
|
|
Ok(post)
|
|
|
|
}
|
|
|
|
|
|
|
|
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},
|
|
|
|
{related_mentions}
|
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-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> {
|
2021-11-19 23:17:04 +00:00
|
|
|
let mut condition = format!(
|
|
|
|
"post.visibility = {visibility_public}",
|
|
|
|
visibility_public=i16::from(&Visibility::Public),
|
|
|
|
);
|
|
|
|
// Create mutable params array
|
|
|
|
// https://github.com/sfackler/rust-postgres/issues/712
|
|
|
|
let mut parameters: Vec<&(dyn ToSql + Sync)> = vec![post_id];
|
|
|
|
if let Some(current_user_id) = current_user_id {
|
|
|
|
condition.push_str(
|
|
|
|
"
|
|
|
|
OR EXISTS (
|
|
|
|
SELECT 1 FROM mention
|
|
|
|
WHERE post_id = post.id AND profile_id = $2
|
|
|
|
)
|
|
|
|
",
|
|
|
|
);
|
|
|
|
parameters.push(current_user_id);
|
|
|
|
};
|
2021-09-22 16:32:44 +00:00
|
|
|
// 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
|
2021-11-19 23:17:04 +00:00
|
|
|
WHERE post.id = $1 AND {condition}
|
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},
|
|
|
|
{related_mentions}
|
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
|
2021-11-19 23:17:04 +00:00
|
|
|
WHERE {condition}
|
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-11-19 23:17:04 +00:00
|
|
|
condition=condition,
|
2021-11-11 18:35:27 +00:00
|
|
|
);
|
|
|
|
let rows = db_client.query(
|
|
|
|
statement.as_str(),
|
2021-11-19 23:17:04 +00:00
|
|
|
¶meters,
|
2021-09-22 16:32:44 +00:00
|
|
|
).await?;
|
|
|
|
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},
|
|
|
|
{related_mentions}
|
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-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},
|
|
|
|
{related_mentions}
|
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-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> {
|
|
|
|
// TODO: create PostUpdateData type
|
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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
|
|
|
|
WHERE ipfs_cid IS NOT NULL AND token_id IS NULL
|
|
|
|
",
|
|
|
|
&[],
|
|
|
|
).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
|
|
|
}
|