Add API endpoints for making and deleting reposts
This commit is contained in:
parent
9b52fb730a
commit
47826628cd
12 changed files with 269 additions and 8 deletions
|
@ -119,6 +119,8 @@ GET /api/v1/statuses/{status_id}
|
|||
GET /api/v1/statuses/{status_id}/context
|
||||
POST /api/v1/statuses/{status_id}/favourite
|
||||
POST /api/v1/statuses/{status_id}/unfavourite
|
||||
POST /api/v1/statuses/{status_id}/reblog
|
||||
POST /api/v1/statuses/{status_id}/unreblog
|
||||
GET /api/v1/timelines/home
|
||||
```
|
||||
|
||||
|
|
3
migrations/V0013__post__add_repost_fields.sql
Normal file
3
migrations/V0013__post__add_repost_fields.sql
Normal file
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE post ADD COLUMN repost_of_id UUID REFERENCES post (id) ON DELETE CASCADE;
|
||||
ALTER TABLE post ADD COLUMN repost_count INTEGER NOT NULL CHECK (repost_count >= 0) DEFAULT 0;
|
||||
ALTER TABLE post ADD CONSTRAINT post_author_id_repost_of_id_key UNIQUE (author_id, repost_of_id);
|
|
@ -34,14 +34,17 @@ CREATE TABLE post (
|
|||
author_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||
content TEXT NOT NULL,
|
||||
in_reply_to_id UUID REFERENCES post (id) ON DELETE CASCADE,
|
||||
repost_of_id UUID REFERENCES post (id) ON DELETE CASCADE,
|
||||
visilibity SMALLINT NOT NULL,
|
||||
reply_count INTEGER NOT NULL CHECK (reply_count >= 0) DEFAULT 0,
|
||||
reaction_count INTEGER NOT NULL CHECK (reaction_count >= 0) DEFAULT 0,
|
||||
repost_count INTEGER NOT NULL CHECK (repost_count >= 0) DEFAULT 0,
|
||||
object_id VARCHAR(200) UNIQUE,
|
||||
ipfs_cid VARCHAR(200),
|
||||
token_id INTEGER,
|
||||
token_tx_id VARCHAR(200),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
UNIQUE (author_id, repost_of_id)
|
||||
);
|
||||
|
||||
CREATE TABLE post_reaction (
|
||||
|
|
|
@ -247,6 +247,7 @@ pub async fn process_note(
|
|||
let post_data = PostCreateData {
|
||||
content,
|
||||
in_reply_to_id,
|
||||
repost_of_id: None,
|
||||
visibility,
|
||||
attachments: attachments,
|
||||
mentions: mentions,
|
||||
|
|
|
@ -16,7 +16,10 @@ use crate::errors::{HttpError, ValidationError};
|
|||
use crate::ethereum::gate::is_allowed_user;
|
||||
use crate::mastodon_api::statuses::types::Status;
|
||||
use crate::mastodon_api::oauth::auth::get_current_user;
|
||||
use crate::models::posts::helpers::get_actions_for_posts;
|
||||
use crate::models::posts::helpers::{
|
||||
get_actions_for_posts,
|
||||
get_reposted_posts,
|
||||
};
|
||||
use crate::models::posts::queries::get_posts_by_author;
|
||||
use crate::models::profiles::queries::{
|
||||
get_followers,
|
||||
|
@ -266,6 +269,7 @@ async fn get_account_statuses(
|
|||
false,
|
||||
false,
|
||||
).await?;
|
||||
get_reposted_posts(db_client, posts.iter_mut().collect()).await?;
|
||||
if let Some(user) = maybe_current_user {
|
||||
get_actions_for_posts(
|
||||
db_client,
|
||||
|
|
|
@ -36,14 +36,17 @@ pub struct Status {
|
|||
pub account: Account,
|
||||
pub content: String,
|
||||
pub in_reply_to_id: Option<Uuid>,
|
||||
pub reblog: Option<Box<Status>>,
|
||||
pub visibility: String,
|
||||
pub replies_count: i32,
|
||||
pub favourites_count: i32,
|
||||
pub reblogs_count: i32,
|
||||
pub media_attachments: Vec<Attachment>,
|
||||
mentions: Vec<Mention>,
|
||||
|
||||
// Authorized user attributes
|
||||
pub favourited: bool,
|
||||
pub reblogged: bool,
|
||||
|
||||
// Extra fields
|
||||
pub ipfs_cid: Option<String>,
|
||||
|
@ -61,6 +64,12 @@ impl Status {
|
|||
.map(|item| Mention::from_profile(item, instance_url))
|
||||
.collect();
|
||||
let account = Account::from_profile(post.author, instance_url);
|
||||
let reblog = if let Some(repost_of) = post.repost_of {
|
||||
let status = Status::from_post(*repost_of, instance_url);
|
||||
Some(Box::new(status))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let visibility = match post.visibility {
|
||||
Visibility::Public => "public",
|
||||
Visibility::Direct => "direct",
|
||||
|
@ -72,12 +81,15 @@ impl Status {
|
|||
account: account,
|
||||
content: post.content,
|
||||
in_reply_to_id: post.in_reply_to_id,
|
||||
reblog: reblog,
|
||||
visibility: visibility.to_string(),
|
||||
replies_count: post.reply_count,
|
||||
favourites_count: post.reaction_count,
|
||||
reblogs_count: post.repost_count,
|
||||
media_attachments: attachments,
|
||||
mentions: mentions,
|
||||
favourited: post.actions.map_or(false, |actions| actions.favourited),
|
||||
favourited: post.actions.as_ref().map_or(false, |actions| actions.favourited),
|
||||
reblogged: post.actions.as_ref().map_or(false, |actions| actions.reposted),
|
||||
ipfs_cid: post.ipfs_cid,
|
||||
token_id: post.token_id,
|
||||
token_tx_id: post.token_tx_id,
|
||||
|
@ -102,6 +114,7 @@ impl From<StatusData> for PostCreateData {
|
|||
Self {
|
||||
content: value.status,
|
||||
in_reply_to_id: value.in_reply_to_id,
|
||||
repost_of_id: None,
|
||||
visibility: Visibility::Public,
|
||||
attachments: value.media_ids.unwrap_or(vec![]),
|
||||
mentions: vec![],
|
||||
|
|
|
@ -24,12 +24,15 @@ use crate::models::posts::mentions::{find_mentioned_profiles, replace_mentions};
|
|||
use crate::models::profiles::queries::get_followers;
|
||||
use crate::models::posts::helpers::{
|
||||
get_actions_for_posts,
|
||||
get_reposted_posts,
|
||||
};
|
||||
use crate::models::posts::queries::{
|
||||
create_post,
|
||||
get_post_by_id,
|
||||
get_thread,
|
||||
find_reposts_by_user,
|
||||
update_post,
|
||||
delete_post,
|
||||
};
|
||||
use crate::models::posts::types::PostCreateData;
|
||||
use crate::models::reactions::queries::{
|
||||
|
@ -123,6 +126,7 @@ async fn get_status(
|
|||
if !can_view_post(maybe_current_user.as_ref(), &post) {
|
||||
return Err(HttpError::NotFoundError("post"));
|
||||
};
|
||||
get_reposted_posts(db_client, vec![&mut post]).await?;
|
||||
if let Some(user) = maybe_current_user {
|
||||
get_actions_for_posts(db_client, &user.id, vec![&mut post]).await?;
|
||||
}
|
||||
|
@ -147,6 +151,7 @@ async fn get_context(
|
|||
&status_id,
|
||||
maybe_current_user.as_ref().map(|user| &user.id),
|
||||
).await?;
|
||||
get_reposted_posts(db_client, posts.iter_mut().collect()).await?;
|
||||
if let Some(user) = maybe_current_user {
|
||||
get_actions_for_posts(
|
||||
db_client,
|
||||
|
@ -182,6 +187,7 @@ async fn favourite(
|
|||
Err(other_error) => return Err(other_error.into()),
|
||||
};
|
||||
let mut post = get_post_by_id(db_client, &status_id).await?;
|
||||
get_reposted_posts(db_client, vec![&mut post]).await?;
|
||||
get_actions_for_posts(db_client, ¤t_user.id, vec![&mut post]).await?;
|
||||
|
||||
if reaction_created {
|
||||
|
@ -221,6 +227,48 @@ async fn unfavourite(
|
|||
other_result => other_result?,
|
||||
}
|
||||
let mut post = get_post_by_id(db_client, &status_id).await?;
|
||||
get_reposted_posts(db_client, vec![&mut post]).await?;
|
||||
get_actions_for_posts(db_client, ¤t_user.id, vec![&mut post]).await?;
|
||||
let status = Status::from_post(post, &config.instance_url());
|
||||
Ok(HttpResponse::Ok().json(status))
|
||||
}
|
||||
|
||||
#[post("/{status_id}/reblog")]
|
||||
async fn reblog(
|
||||
auth: BearerAuth,
|
||||
config: web::Data<Config>,
|
||||
db_pool: web::Data<Pool>,
|
||||
web::Path(status_id): web::Path<Uuid>,
|
||||
) -> 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 repost_data = PostCreateData {
|
||||
repost_of_id: Some(status_id),
|
||||
..Default::default()
|
||||
};
|
||||
create_post(db_client, ¤t_user.id, repost_data).await?;
|
||||
let mut post = get_post_by_id(db_client, &status_id).await?;
|
||||
get_reposted_posts(db_client, vec![&mut post]).await?;
|
||||
get_actions_for_posts(db_client, ¤t_user.id, vec![&mut post]).await?;
|
||||
let status = Status::from_post(post, &config.instance_url());
|
||||
Ok(HttpResponse::Ok().json(status))
|
||||
}
|
||||
|
||||
#[post("/{status_id}/unreblog")]
|
||||
async fn unreblog(
|
||||
auth: BearerAuth,
|
||||
config: web::Data<Config>,
|
||||
db_pool: web::Data<Pool>,
|
||||
web::Path(status_id): web::Path<Uuid>,
|
||||
) -> 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 reposts = find_reposts_by_user(db_client, ¤t_user.id, &[status_id]).await?;
|
||||
let repost_id = reposts.first().ok_or(HttpError::NotFoundError("post"))?;
|
||||
// Ignore returned data because reposts don't have attached files
|
||||
delete_post(db_client, repost_id).await?;
|
||||
let mut post = get_post_by_id(db_client, &status_id).await?;
|
||||
get_reposted_posts(db_client, vec![&mut post]).await?;
|
||||
get_actions_for_posts(db_client, ¤t_user.id, vec![&mut post]).await?;
|
||||
let status = Status::from_post(post, &config.instance_url());
|
||||
Ok(HttpResponse::Ok().json(status))
|
||||
|
@ -284,6 +332,7 @@ async fn make_permanent(
|
|||
// Update post
|
||||
post.ipfs_cid = Some(post_metadata_cid);
|
||||
update_post(db_client, &post).await?;
|
||||
get_reposted_posts(db_client, vec![&mut post]).await?;
|
||||
get_actions_for_posts(db_client, ¤t_user.id, vec![&mut post]).await?;
|
||||
let status = Status::from_post(post, &config.instance_url());
|
||||
Ok(HttpResponse::Ok().json(status))
|
||||
|
@ -326,6 +375,8 @@ pub fn status_api_scope() -> Scope {
|
|||
.service(get_context)
|
||||
.service(favourite)
|
||||
.service(unfavourite)
|
||||
.service(reblog)
|
||||
.service(unreblog)
|
||||
.service(make_permanent)
|
||||
.service(get_signature)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@ use crate::database::{Pool, get_database_client};
|
|||
use crate::errors::HttpError;
|
||||
use crate::mastodon_api::oauth::auth::get_current_user;
|
||||
use crate::mastodon_api::statuses::types::Status;
|
||||
use crate::models::posts::helpers::get_actions_for_posts;
|
||||
use crate::models::posts::helpers::{
|
||||
get_actions_for_posts,
|
||||
get_reposted_posts,
|
||||
};
|
||||
use crate::models::posts::queries::get_home_timeline;
|
||||
|
||||
/// https://docs.joinmastodon.org/methods/timelines/
|
||||
|
@ -19,6 +22,7 @@ async fn home_timeline(
|
|||
let db_client = &**get_database_client(&db_pool).await?;
|
||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||
let mut posts = get_home_timeline(db_client, ¤t_user.id).await?;
|
||||
get_reposted_posts(db_client, posts.iter_mut().collect()).await?;
|
||||
get_actions_for_posts(
|
||||
db_client,
|
||||
¤t_user.id,
|
||||
|
|
|
@ -4,18 +4,55 @@ use uuid::Uuid;
|
|||
use crate::errors::DatabaseError;
|
||||
use crate::models::reactions::queries::find_favourited_by_user;
|
||||
use crate::models::users::types::User;
|
||||
use super::queries::{get_posts, find_reposted_by_user};
|
||||
use super::types::{Post, PostActions, Visibility};
|
||||
|
||||
pub async fn get_reposted_posts(
|
||||
db_client: &impl GenericClient,
|
||||
posts: Vec<&mut Post>,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let reposted_ids: Vec<Uuid> = posts.iter()
|
||||
.filter_map(|post| post.repost_of_id)
|
||||
.collect();
|
||||
let mut reposted = get_posts(db_client, reposted_ids).await?;
|
||||
for post in posts {
|
||||
if let Some(ref repost_of_id) = post.repost_of_id {
|
||||
let index = reposted.iter()
|
||||
.position(|post| post.id == *repost_of_id)
|
||||
.ok_or(DatabaseError::NotFound("post"))?;
|
||||
let repost_of = reposted.swap_remove(index);
|
||||
post.repost_of = Some(Box::new(repost_of));
|
||||
};
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_actions_for_posts(
|
||||
db_client: &impl GenericClient,
|
||||
user_id: &Uuid,
|
||||
posts: Vec<&mut Post>,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let posts_ids: Vec<Uuid> = posts.iter().map(|post| post.id).collect();
|
||||
let posts_ids: Vec<Uuid> = posts.iter()
|
||||
.map(|post| post.id)
|
||||
.chain(
|
||||
posts.iter()
|
||||
.filter_map(|post| post.repost_of.as_ref())
|
||||
.map(|post| post.id)
|
||||
)
|
||||
.collect();
|
||||
let favourites = find_favourited_by_user(db_client, user_id, &posts_ids).await?;
|
||||
let reposted = find_reposted_by_user(db_client, user_id, &posts_ids).await?;
|
||||
for post in posts {
|
||||
if let Some(ref mut repost_of) = post.repost_of {
|
||||
let actions = PostActions {
|
||||
favourited: favourites.contains(&repost_of.id),
|
||||
reposted: reposted.contains(&repost_of.id),
|
||||
};
|
||||
repost_of.actions = Some(actions);
|
||||
};
|
||||
let actions = PostActions {
|
||||
favourited: favourites.contains(&post.id),
|
||||
reposted: reposted.contains(&post.id),
|
||||
};
|
||||
post.actions = Some(actions);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use postgres_types::ToSql;
|
|||
use tokio_postgres::GenericClient;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::catch_unique_violation;
|
||||
use crate::errors::DatabaseError;
|
||||
use crate::models::attachments::types::DbMediaAttachment;
|
||||
use crate::models::cleanup::{
|
||||
|
@ -76,6 +77,33 @@ pub async fn get_home_timeline(
|
|||
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},
|
||||
{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)
|
||||
}
|
||||
|
||||
pub async fn get_posts_by_author(
|
||||
db_client: &impl GenericClient,
|
||||
account_id: &Uuid,
|
||||
|
@ -126,28 +154,43 @@ pub async fn create_post(
|
|||
let transaction = db_client.transaction().await?;
|
||||
let post_id = uuid::Uuid::new_v4();
|
||||
let created_at = data.created_at.unwrap_or(Utc::now());
|
||||
let post_row = transaction.query_one(
|
||||
// Reposting of other reposts or non-public posts is not allowed
|
||||
let insert_statement = format!(
|
||||
"
|
||||
INSERT INTO post (
|
||||
id, author_id, content,
|
||||
in_reply_to_id,
|
||||
repost_of_id,
|
||||
visibility,
|
||||
object_id,
|
||||
created_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
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}
|
||||
)
|
||||
)
|
||||
RETURNING post
|
||||
",
|
||||
visibility_public=i16::from(&Visibility::Public),
|
||||
);
|
||||
let maybe_post_row = transaction.query_opt(
|
||||
insert_statement.as_str(),
|
||||
&[
|
||||
&post_id,
|
||||
&author_id,
|
||||
&data.content,
|
||||
&data.in_reply_to_id,
|
||||
&data.repost_of_id,
|
||||
&data.visibility,
|
||||
&data.object_id,
|
||||
&created_at,
|
||||
],
|
||||
).await?;
|
||||
).await.map_err(catch_unique_violation("post"))?;
|
||||
let post_row = maybe_post_row.ok_or(DatabaseError::NotFound("post"))?;
|
||||
let db_post: DbPost = post_row.try_get("post")?;
|
||||
// Create links to attachments
|
||||
let attachments_rows = transaction.query(
|
||||
|
@ -201,6 +244,9 @@ pub async fn create_post(
|
|||
).await?;
|
||||
}
|
||||
}
|
||||
if let Some(repost_of_id) = &db_post.repost_of_id {
|
||||
update_repost_count(&transaction, repost_of_id, 1).await?;
|
||||
};
|
||||
|
||||
transaction.commit().await?;
|
||||
let post = Post::new(db_post, author, db_attachments, db_mentions)?;
|
||||
|
@ -426,6 +472,68 @@ pub async fn update_reaction_count(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub async fn get_token_waitlist(
|
||||
db_client: &impl GenericClient,
|
||||
) -> Result<Vec<Uuid>, DatabaseError> {
|
||||
|
@ -490,6 +598,9 @@ pub async fn delete_post(
|
|||
if let Some(parent_id) = &db_post.in_reply_to_id {
|
||||
update_reply_count(&transaction, parent_id, -1).await?;
|
||||
}
|
||||
if let Some(repost_of_id) = &db_post.repost_of_id {
|
||||
update_repost_count(&transaction, repost_of_id, -1).await?;
|
||||
};
|
||||
update_post_count(&transaction, &db_post.author_id, -1).await?;
|
||||
let orphaned_files = find_orphaned_files(&transaction, files).await?;
|
||||
let orphaned_ipfs_objects = find_orphaned_ipfs_objects(&transaction, ipfs_objects).await?;
|
||||
|
|
|
@ -18,6 +18,10 @@ pub enum Visibility {
|
|||
Direct,
|
||||
}
|
||||
|
||||
impl Default for Visibility {
|
||||
fn default() -> Self { Self::Public }
|
||||
}
|
||||
|
||||
impl From<&Visibility> for i16 {
|
||||
fn from(value: &Visibility) -> i16 {
|
||||
match value {
|
||||
|
@ -50,9 +54,11 @@ pub struct DbPost {
|
|||
pub author_id: Uuid,
|
||||
pub content: String,
|
||||
pub in_reply_to_id: Option<Uuid>,
|
||||
pub repost_of_id: Option<Uuid>,
|
||||
pub visibility: Visibility,
|
||||
pub reply_count: i32,
|
||||
pub reaction_count: i32,
|
||||
pub repost_count: i32,
|
||||
pub object_id: Option<String>,
|
||||
pub ipfs_cid: Option<String>,
|
||||
pub token_id: Option<i32>,
|
||||
|
@ -63,6 +69,7 @@ pub struct DbPost {
|
|||
// List of user's actions
|
||||
pub struct PostActions {
|
||||
pub favourited: bool,
|
||||
pub reposted: bool,
|
||||
}
|
||||
|
||||
pub struct Post {
|
||||
|
@ -70,9 +77,11 @@ pub struct Post {
|
|||
pub author: DbActorProfile,
|
||||
pub content: String,
|
||||
pub in_reply_to_id: Option<Uuid>,
|
||||
pub repost_of_id: Option<Uuid>,
|
||||
pub visibility: Visibility,
|
||||
pub reply_count: i32,
|
||||
pub reaction_count: i32,
|
||||
pub repost_count: i32,
|
||||
pub attachments: Vec<DbMediaAttachment>,
|
||||
pub mentions: Vec<DbActorProfile>,
|
||||
pub object_id: Option<String>,
|
||||
|
@ -80,7 +89,9 @@ pub struct Post {
|
|||
pub token_id: Option<i32>,
|
||||
pub token_tx_id: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
|
||||
pub actions: Option<PostActions>,
|
||||
pub repost_of: Option<Box<Post>>,
|
||||
}
|
||||
|
||||
impl Post {
|
||||
|
@ -102,9 +113,11 @@ impl Post {
|
|||
author: db_author,
|
||||
content: db_post.content,
|
||||
in_reply_to_id: db_post.in_reply_to_id,
|
||||
repost_of_id: db_post.repost_of_id,
|
||||
visibility: db_post.visibility,
|
||||
reply_count: db_post.reply_count,
|
||||
reaction_count: db_post.reaction_count,
|
||||
repost_count: db_post.repost_count,
|
||||
attachments: db_attachments,
|
||||
mentions: db_mentions,
|
||||
object_id: db_post.object_id,
|
||||
|
@ -113,6 +126,7 @@ impl Post {
|
|||
token_tx_id: db_post.token_tx_id,
|
||||
created_at: db_post.created_at,
|
||||
actions: None,
|
||||
repost_of: None,
|
||||
};
|
||||
Ok(post)
|
||||
}
|
||||
|
@ -137,9 +151,11 @@ impl Default for Post {
|
|||
author: Default::default(),
|
||||
content: "".to_string(),
|
||||
in_reply_to_id: None,
|
||||
repost_of_id: None,
|
||||
visibility: Visibility::Public,
|
||||
reply_count: 0,
|
||||
reaction_count: 0,
|
||||
repost_count: 0,
|
||||
attachments: vec![],
|
||||
mentions: vec![],
|
||||
object_id: None,
|
||||
|
@ -148,6 +164,7 @@ impl Default for Post {
|
|||
token_tx_id: None,
|
||||
created_at: Utc::now(),
|
||||
actions: None,
|
||||
repost_of: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -166,9 +183,11 @@ impl TryFrom<&Row> for Post {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PostCreateData {
|
||||
pub content: String,
|
||||
pub in_reply_to_id: Option<Uuid>,
|
||||
pub repost_of_id: Option<Uuid>,
|
||||
pub visibility: Visibility,
|
||||
pub attachments: Vec<Uuid>,
|
||||
pub mentions: Vec<Uuid>,
|
||||
|
@ -198,6 +217,7 @@ mod tests {
|
|||
let mut post_data_1 = PostCreateData {
|
||||
content: " ".to_string(),
|
||||
in_reply_to_id: None,
|
||||
repost_of_id: None,
|
||||
visibility: Visibility::Public,
|
||||
attachments: vec![],
|
||||
mentions: vec![],
|
||||
|
@ -212,6 +232,7 @@ mod tests {
|
|||
let mut post_data_2 = PostCreateData {
|
||||
content: "test ".to_string(),
|
||||
in_reply_to_id: None,
|
||||
repost_of_id: None,
|
||||
visibility: Visibility::Public,
|
||||
attachments: vec![],
|
||||
mentions: vec![],
|
||||
|
|
|
@ -279,6 +279,17 @@ pub async fn delete_profile(
|
|||
",
|
||||
&[&profile_id],
|
||||
).await?;
|
||||
transaction.execute(
|
||||
"
|
||||
UPDATE post
|
||||
SET repost_count = repost_count - 1
|
||||
FROM post AS repost
|
||||
WHERE
|
||||
repost.repost_of_id = post.id
|
||||
AND repost.author_id = $1
|
||||
",
|
||||
&[&profile_id],
|
||||
).await?;
|
||||
// Delete profile
|
||||
let deleted_count = transaction.execute(
|
||||
"
|
||||
|
|
Loading…
Reference in a new issue