2021-10-17 16:55:33 +00:00
|
|
|
/// https://docs.joinmastodon.org/methods/statuses/
|
2022-01-07 20:41:46 +00:00
|
|
|
use std::convert::TryFrom;
|
|
|
|
|
2021-12-04 00:42:36 +00:00
|
|
|
use actix_web::{delete, get, post, web, HttpResponse, Scope};
|
2021-10-05 18:10:14 +00:00
|
|
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
2021-04-09 00:22:17 +00:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
2021-10-29 19:21:26 +00:00
|
|
|
use crate::activitypub::activity::{
|
|
|
|
create_activity_note,
|
|
|
|
create_activity_like,
|
2021-12-15 01:00:42 +00:00
|
|
|
create_activity_undo_like,
|
2021-11-25 21:28:06 +00:00
|
|
|
create_activity_announce,
|
2021-12-16 17:32:49 +00:00
|
|
|
create_activity_undo_announce,
|
2021-12-04 00:42:36 +00:00
|
|
|
create_activity_delete_note,
|
2021-10-29 19:21:26 +00:00
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
use crate::activitypub::deliverer::deliver_activity;
|
|
|
|
use crate::config::Config;
|
|
|
|
use crate::database::{Pool, get_database_client};
|
2022-01-08 18:49:39 +00:00
|
|
|
use crate::errors::{DatabaseError, HttpError, ValidationError};
|
2021-04-09 00:22:17 +00:00
|
|
|
use crate::ethereum::nft::create_mint_signature;
|
|
|
|
use crate::ipfs::store as ipfs_store;
|
2022-01-11 18:15:20 +00:00
|
|
|
use crate::ipfs::posts::PostMetadata;
|
2021-04-09 00:22:17 +00:00
|
|
|
use crate::ipfs::utils::{IPFS_LOGO, get_ipfs_url};
|
2021-10-05 18:10:14 +00:00
|
|
|
use crate::mastodon_api::oauth::auth::get_current_user;
|
2021-09-28 21:44:31 +00:00
|
|
|
use crate::models::attachments::queries::set_attachment_ipfs_cid;
|
2021-11-20 01:10:56 +00:00
|
|
|
use crate::models::posts::helpers::can_view_post;
|
2021-11-10 17:00:39 +00:00
|
|
|
use crate::models::posts::mentions::{find_mentioned_profiles, replace_mentions};
|
2021-12-07 23:28:58 +00:00
|
|
|
use crate::models::posts::tags::{find_tags, replace_tags};
|
2021-10-18 22:35:52 +00:00
|
|
|
use crate::models::posts::helpers::{
|
|
|
|
get_actions_for_posts,
|
2021-11-24 16:39:30 +00:00
|
|
|
get_reposted_posts,
|
2021-10-18 22:35:52 +00:00
|
|
|
};
|
2021-09-22 16:32:44 +00:00
|
|
|
use crate::models::posts::queries::{
|
|
|
|
create_post,
|
|
|
|
get_post_by_id,
|
|
|
|
get_thread,
|
2021-11-24 16:39:30 +00:00
|
|
|
find_reposts_by_user,
|
2021-09-22 16:32:44 +00:00
|
|
|
update_post,
|
2021-11-24 16:39:30 +00:00
|
|
|
delete_post,
|
2021-09-22 16:32:44 +00:00
|
|
|
};
|
2022-01-08 18:49:39 +00:00
|
|
|
use crate::models::posts::types::{PostCreateData, Visibility};
|
2021-10-18 23:42:56 +00:00
|
|
|
use crate::models::reactions::queries::{
|
|
|
|
create_reaction,
|
|
|
|
delete_reaction,
|
|
|
|
};
|
2021-12-31 16:43:14 +00:00
|
|
|
use super::helpers::{
|
2022-01-15 14:53:42 +00:00
|
|
|
get_announce_recipients,
|
|
|
|
get_like_recipients,
|
|
|
|
get_note_recipients,
|
2021-12-31 16:43:14 +00:00
|
|
|
Audience,
|
|
|
|
};
|
2021-12-03 18:44:01 +00:00
|
|
|
use super::types::{Status, StatusData, TransactionData};
|
2021-04-09 00:22:17 +00:00
|
|
|
|
|
|
|
#[post("")]
|
|
|
|
async fn create_status(
|
2021-10-05 18:10:14 +00:00
|
|
|
auth: BearerAuth,
|
2021-04-09 00:22:17 +00:00
|
|
|
config: web::Data<Config>,
|
|
|
|
db_pool: web::Data<Pool>,
|
|
|
|
data: web::Json<StatusData>,
|
|
|
|
) -> Result<HttpResponse, HttpError> {
|
|
|
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
2021-10-05 18:10:14 +00:00
|
|
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
2021-11-10 17:00:39 +00:00
|
|
|
let instance = config.instance();
|
2022-01-07 20:41:46 +00:00
|
|
|
let mut post_data = PostCreateData::try_from(data.into_inner())?;
|
2021-12-27 19:20:21 +00:00
|
|
|
post_data.clean()?;
|
2021-11-10 17:00:39 +00:00
|
|
|
// Mentions
|
|
|
|
let mention_map = find_mentioned_profiles(
|
|
|
|
db_client,
|
|
|
|
&instance.host(),
|
|
|
|
&post_data.content,
|
|
|
|
).await?;
|
|
|
|
post_data.content = replace_mentions(
|
|
|
|
&mention_map,
|
|
|
|
&instance.host(),
|
|
|
|
&instance.url(),
|
|
|
|
&post_data.content,
|
|
|
|
);
|
2022-01-15 16:36:25 +00:00
|
|
|
post_data.mentions.extend(mention_map.values()
|
|
|
|
.map(|profile| profile.id));
|
|
|
|
post_data.mentions.sort();
|
|
|
|
post_data.mentions.dedup();
|
2022-01-08 18:49:39 +00:00
|
|
|
// Tags
|
2021-12-07 23:28:58 +00:00
|
|
|
post_data.tags = find_tags(&post_data.content);
|
2021-12-12 19:11:04 +00:00
|
|
|
post_data.content = replace_tags(
|
|
|
|
&instance.url(),
|
|
|
|
&post_data.content,
|
|
|
|
&post_data.tags,
|
|
|
|
);
|
2022-01-08 18:49:39 +00:00
|
|
|
// Reply validation
|
|
|
|
let maybe_in_reply_to = if let Some(in_reply_to_id) = post_data.in_reply_to_id.as_ref() {
|
|
|
|
let in_reply_to = match get_post_by_id(db_client, in_reply_to_id).await {
|
|
|
|
Ok(post) => post,
|
|
|
|
Err(DatabaseError::NotFound(_)) => {
|
|
|
|
return Err(ValidationError("parent post does not exist").into());
|
|
|
|
},
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
|
|
|
if post_data.visibility != in_reply_to.visibility {
|
|
|
|
return Err(ValidationError("post visibility doesn't match the parent").into());
|
|
|
|
};
|
|
|
|
if post_data.visibility != Visibility::Public {
|
2022-01-21 11:43:23 +00:00
|
|
|
let mut in_reply_to_audience: Vec<_> = in_reply_to.mentions.iter()
|
2022-01-08 18:49:39 +00:00
|
|
|
.map(|profile| profile.id).collect();
|
2022-01-21 11:43:23 +00:00
|
|
|
in_reply_to_audience.push(in_reply_to.author.id);
|
|
|
|
if !post_data.mentions.iter().all(|id| in_reply_to_audience.contains(id)) {
|
2022-01-08 18:49:39 +00:00
|
|
|
return Err(ValidationError("audience can't be expanded").into());
|
|
|
|
};
|
|
|
|
};
|
|
|
|
Some(in_reply_to)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
// Create post
|
2021-12-31 17:15:00 +00:00
|
|
|
let mut post = create_post(db_client, ¤t_user.id, post_data).await?;
|
2022-01-08 18:49:39 +00:00
|
|
|
post.in_reply_to = maybe_in_reply_to.map(|mut in_reply_to| {
|
|
|
|
in_reply_to.reply_count += 1;
|
|
|
|
Box::new(in_reply_to)
|
|
|
|
});
|
2021-04-09 00:22:17 +00:00
|
|
|
// Federate
|
2021-10-09 12:53:53 +00:00
|
|
|
let activity = create_activity_note(
|
2021-11-11 21:51:47 +00:00
|
|
|
&instance.host(),
|
2021-11-10 17:00:39 +00:00
|
|
|
&instance.url(),
|
2021-10-09 12:53:53 +00:00
|
|
|
&post,
|
|
|
|
);
|
2022-01-15 14:53:42 +00:00
|
|
|
let recipients = get_note_recipients(db_client, ¤t_user, &post).await?;
|
2021-10-30 22:35:18 +00:00
|
|
|
deliver_activity(&config, ¤t_user, activity, recipients);
|
2021-11-10 17:00:39 +00:00
|
|
|
let status = Status::from_post(post, &instance.url());
|
2021-04-09 00:22:17 +00:00
|
|
|
Ok(HttpResponse::Created().json(status))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/{status_id}")]
|
|
|
|
async fn get_status(
|
2021-10-18 22:35:52 +00:00
|
|
|
auth: Option<BearerAuth>,
|
2021-04-09 00:22:17 +00:00
|
|
|
config: web::Data<Config>,
|
|
|
|
db_pool: web::Data<Pool>,
|
|
|
|
web::Path(status_id): web::Path<Uuid>,
|
|
|
|
) -> Result<HttpResponse, HttpError> {
|
|
|
|
let db_client = &**get_database_client(&db_pool).await?;
|
2021-10-18 22:35:52 +00:00
|
|
|
let maybe_current_user = match auth {
|
|
|
|
Some(auth) => Some(get_current_user(db_client, auth.token()).await?),
|
|
|
|
None => None,
|
|
|
|
};
|
|
|
|
let mut post = get_post_by_id(db_client, &status_id).await?;
|
2022-01-06 13:45:07 +00:00
|
|
|
if !can_view_post(db_client, maybe_current_user.as_ref(), &post).await? {
|
2021-11-20 01:10:56 +00:00
|
|
|
return Err(HttpError::NotFoundError("post"));
|
|
|
|
};
|
2021-11-24 16:39:30 +00:00
|
|
|
get_reposted_posts(db_client, vec![&mut post]).await?;
|
2021-10-18 22:35:52 +00:00
|
|
|
if let Some(user) = maybe_current_user {
|
2021-11-24 00:33:22 +00:00
|
|
|
get_actions_for_posts(db_client, &user.id, vec![&mut post]).await?;
|
2021-10-18 22:35:52 +00:00
|
|
|
}
|
2021-04-09 00:22:17 +00:00
|
|
|
let status = Status::from_post(post, &config.instance_url());
|
|
|
|
Ok(HttpResponse::Ok().json(status))
|
|
|
|
}
|
|
|
|
|
2021-12-04 00:42:36 +00:00
|
|
|
#[delete("/{status_id}")]
|
|
|
|
async fn delete_status(
|
|
|
|
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 post = get_post_by_id(db_client, &status_id).await?;
|
|
|
|
if post.author.id != current_user.id {
|
|
|
|
return Err(HttpError::PermissionError);
|
|
|
|
};
|
|
|
|
let deletion_queue = delete_post(db_client, &status_id).await?;
|
|
|
|
let config_clone = config.clone();
|
|
|
|
actix_rt::spawn(async move {
|
|
|
|
deletion_queue.process(&config_clone).await;
|
|
|
|
});
|
|
|
|
|
|
|
|
let activity = create_activity_delete_note(
|
|
|
|
&config.instance_url(),
|
|
|
|
&post,
|
|
|
|
);
|
2022-01-15 14:53:42 +00:00
|
|
|
let recipients = get_note_recipients(db_client, ¤t_user, &post).await?;
|
2021-12-04 00:42:36 +00:00
|
|
|
deliver_activity(&config, ¤t_user, activity, recipients);
|
|
|
|
|
|
|
|
Ok(HttpResponse::NoContent().finish())
|
|
|
|
}
|
|
|
|
|
2021-09-22 16:32:44 +00:00
|
|
|
#[get("/{status_id}/context")]
|
|
|
|
async fn get_context(
|
2021-10-18 22:35:52 +00:00
|
|
|
auth: Option<BearerAuth>,
|
2021-09-22 16:32:44 +00:00
|
|
|
config: web::Data<Config>,
|
|
|
|
db_pool: web::Data<Pool>,
|
|
|
|
web::Path(status_id): web::Path<Uuid>,
|
|
|
|
) -> Result<HttpResponse, HttpError> {
|
|
|
|
let db_client = &**get_database_client(&db_pool).await?;
|
2021-10-18 22:35:52 +00:00
|
|
|
let maybe_current_user = match auth {
|
|
|
|
Some(auth) => Some(get_current_user(db_client, auth.token()).await?),
|
|
|
|
None => None,
|
|
|
|
};
|
2021-11-19 23:17:04 +00:00
|
|
|
let mut posts = get_thread(
|
|
|
|
db_client,
|
|
|
|
&status_id,
|
|
|
|
maybe_current_user.as_ref().map(|user| &user.id),
|
|
|
|
).await?;
|
2021-11-24 16:39:30 +00:00
|
|
|
get_reposted_posts(db_client, posts.iter_mut().collect()).await?;
|
2021-10-18 22:35:52 +00:00
|
|
|
if let Some(user) = maybe_current_user {
|
|
|
|
get_actions_for_posts(
|
|
|
|
db_client,
|
|
|
|
&user.id,
|
|
|
|
posts.iter_mut().collect(),
|
|
|
|
).await?;
|
|
|
|
}
|
|
|
|
let statuses: Vec<Status> = posts
|
2021-09-22 16:32:44 +00:00
|
|
|
.into_iter()
|
|
|
|
.map(|post| Status::from_post(post, &config.instance_url()))
|
|
|
|
.collect();
|
|
|
|
Ok(HttpResponse::Ok().json(statuses))
|
|
|
|
}
|
|
|
|
|
2021-10-17 16:55:33 +00:00
|
|
|
#[post("/{status_id}/favourite")]
|
|
|
|
async fn favourite(
|
|
|
|
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?;
|
2021-12-01 14:46:09 +00:00
|
|
|
let mut post = get_post_by_id(db_client, &status_id).await?;
|
2022-01-15 15:25:37 +00:00
|
|
|
if !post.is_public() {
|
2021-11-20 01:10:56 +00:00
|
|
|
return Err(HttpError::NotFoundError("post"));
|
|
|
|
};
|
2021-12-15 01:00:42 +00:00
|
|
|
let maybe_reaction_created = match create_reaction(
|
2021-12-14 22:59:15 +00:00
|
|
|
db_client, ¤t_user.id, &status_id, None,
|
2021-10-29 19:21:26 +00:00
|
|
|
).await {
|
2021-12-15 01:00:42 +00:00
|
|
|
Ok(reaction) => {
|
2021-12-01 14:46:09 +00:00
|
|
|
post.reaction_count += 1;
|
2021-12-15 01:00:42 +00:00
|
|
|
Some(reaction)
|
2021-12-01 14:46:09 +00:00
|
|
|
},
|
2021-12-15 01:00:42 +00:00
|
|
|
Err(DatabaseError::AlreadyExists(_)) => None, // post already favourited
|
2021-10-29 19:21:26 +00:00
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
2021-11-24 16:39:30 +00:00
|
|
|
get_reposted_posts(db_client, vec![&mut post]).await?;
|
2021-11-24 00:33:22 +00:00
|
|
|
get_actions_for_posts(db_client, ¤t_user.id, vec![&mut post]).await?;
|
2021-10-29 19:21:26 +00:00
|
|
|
|
2021-12-15 01:00:42 +00:00
|
|
|
if let Some(reaction) = maybe_reaction_created {
|
2022-01-04 21:36:45 +00:00
|
|
|
// Federate
|
2021-12-31 16:43:14 +00:00
|
|
|
let Audience { recipients, primary_recipient } =
|
2022-01-15 14:53:42 +00:00
|
|
|
get_like_recipients(db_client, &config.instance_url(), &post).await?;
|
2022-01-13 21:29:10 +00:00
|
|
|
let note_id = post.get_object_id(&config.instance_url());
|
2022-01-04 21:36:45 +00:00
|
|
|
let activity = create_activity_like(
|
|
|
|
&config.instance_url(),
|
|
|
|
¤t_user.profile,
|
2022-01-13 21:29:10 +00:00
|
|
|
¬e_id,
|
2022-01-04 21:36:45 +00:00
|
|
|
&reaction.id,
|
|
|
|
&primary_recipient,
|
|
|
|
);
|
|
|
|
deliver_activity(&config, ¤t_user, activity, recipients);
|
2021-10-29 19:21:26 +00:00
|
|
|
}
|
|
|
|
|
2021-10-17 16:55:33 +00:00
|
|
|
let status = Status::from_post(post, &config.instance_url());
|
|
|
|
Ok(HttpResponse::Ok().json(status))
|
|
|
|
}
|
|
|
|
|
2021-10-18 23:42:56 +00:00
|
|
|
#[post("/{status_id}/unfavourite")]
|
|
|
|
async fn unfavourite(
|
|
|
|
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?;
|
2021-12-01 14:46:09 +00:00
|
|
|
let mut post = get_post_by_id(db_client, &status_id).await?;
|
2021-12-15 01:00:42 +00:00
|
|
|
let maybe_reaction_deleted = match delete_reaction(
|
|
|
|
db_client, ¤t_user.id, &status_id,
|
|
|
|
).await {
|
|
|
|
Ok(reaction_id) => {
|
|
|
|
post.reaction_count -= 1;
|
|
|
|
Some(reaction_id)
|
|
|
|
},
|
|
|
|
Err(DatabaseError::NotFound(_)) => None, // post not favourited
|
2021-12-01 14:46:09 +00:00
|
|
|
Err(other_error) => return Err(other_error.into()),
|
2021-12-15 01:00:42 +00:00
|
|
|
};
|
2021-11-24 16:39:30 +00:00
|
|
|
get_reposted_posts(db_client, vec![&mut post]).await?;
|
|
|
|
get_actions_for_posts(db_client, ¤t_user.id, vec![&mut post]).await?;
|
2021-12-15 01:00:42 +00:00
|
|
|
|
|
|
|
if let Some(reaction_id) = maybe_reaction_deleted {
|
2022-01-04 21:36:45 +00:00
|
|
|
// Federate
|
2021-12-31 16:43:14 +00:00
|
|
|
let Audience { recipients, primary_recipient } =
|
2022-01-15 14:53:42 +00:00
|
|
|
get_like_recipients(db_client, &config.instance_url(), &post).await?;
|
2022-01-04 21:36:45 +00:00
|
|
|
let activity = create_activity_undo_like(
|
|
|
|
&config.instance_url(),
|
|
|
|
¤t_user.profile,
|
|
|
|
&reaction_id,
|
|
|
|
&primary_recipient,
|
|
|
|
);
|
|
|
|
deliver_activity(&config, ¤t_user, activity, recipients);
|
2021-12-15 01:00:42 +00:00
|
|
|
};
|
|
|
|
|
2021-11-24 16:39:30 +00:00
|
|
|
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?;
|
2022-01-06 15:35:59 +00:00
|
|
|
let mut post = get_post_by_id(db_client, &status_id).await?;
|
2022-01-15 15:25:37 +00:00
|
|
|
if !post.is_public() {
|
2022-01-06 15:35:59 +00:00
|
|
|
return Err(HttpError::NotFoundError("post"));
|
|
|
|
};
|
2021-11-24 16:39:30 +00:00
|
|
|
let repost_data = PostCreateData {
|
|
|
|
repost_of_id: Some(status_id),
|
|
|
|
..Default::default()
|
|
|
|
};
|
2021-12-16 17:32:49 +00:00
|
|
|
let repost = create_post(db_client, ¤t_user.id, repost_data).await?;
|
2022-01-06 15:35:59 +00:00
|
|
|
post.repost_count += 1;
|
2021-11-24 16:39:30 +00:00
|
|
|
get_reposted_posts(db_client, vec![&mut post]).await?;
|
|
|
|
get_actions_for_posts(db_client, ¤t_user.id, vec![&mut post]).await?;
|
2021-11-25 21:28:06 +00:00
|
|
|
|
|
|
|
// Federate
|
2021-12-31 16:43:14 +00:00
|
|
|
let Audience { recipients, .. } =
|
2022-01-15 14:53:42 +00:00
|
|
|
get_announce_recipients(db_client, &config.instance_url(), ¤t_user, &post).await?;
|
2021-11-26 22:53:40 +00:00
|
|
|
let activity = create_activity_announce(
|
|
|
|
&config.instance_url(),
|
|
|
|
¤t_user.profile,
|
|
|
|
&post,
|
2021-12-16 17:32:49 +00:00
|
|
|
&repost.id,
|
2021-11-26 22:53:40 +00:00
|
|
|
);
|
|
|
|
deliver_activity(&config, ¤t_user, activity, recipients);
|
2021-11-25 21:28:06 +00:00
|
|
|
|
2021-11-24 16:39:30 +00:00
|
|
|
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?;
|
2021-11-24 00:33:22 +00:00
|
|
|
get_actions_for_posts(db_client, ¤t_user.id, vec![&mut post]).await?;
|
2021-12-16 17:32:49 +00:00
|
|
|
|
|
|
|
// Federate
|
2021-12-31 16:43:14 +00:00
|
|
|
let Audience { recipients, primary_recipient } =
|
2022-01-15 14:53:42 +00:00
|
|
|
get_announce_recipients(db_client, &config.instance_url(), ¤t_user, &post).await?;
|
2021-12-25 00:05:17 +00:00
|
|
|
let activity = create_activity_undo_announce(
|
|
|
|
&config.instance_url(),
|
|
|
|
¤t_user.profile,
|
2021-12-28 18:34:13 +00:00
|
|
|
repost_id,
|
2022-01-04 21:36:45 +00:00
|
|
|
&primary_recipient,
|
2021-12-25 00:05:17 +00:00
|
|
|
);
|
2021-12-16 17:32:49 +00:00
|
|
|
deliver_activity(&config, ¤t_user, activity, recipients);
|
|
|
|
|
2021-10-18 23:42:56 +00:00
|
|
|
let status = Status::from_post(post, &config.instance_url());
|
|
|
|
Ok(HttpResponse::Ok().json(status))
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
#[post("/{status_id}/make_permanent")]
|
|
|
|
async fn make_permanent(
|
2021-10-05 18:10:14 +00:00
|
|
|
auth: BearerAuth,
|
2021-04-09 00:22:17 +00:00
|
|
|
config: web::Data<Config>,
|
|
|
|
db_pool: web::Data<Pool>,
|
|
|
|
web::Path(status_id): web::Path<Uuid>,
|
|
|
|
) -> Result<HttpResponse, HttpError> {
|
|
|
|
let db_client = &**get_database_client(&db_pool).await?;
|
2021-10-18 22:35:52 +00:00
|
|
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
2021-04-09 00:22:17 +00:00
|
|
|
let mut post = get_post_by_id(db_client, &status_id).await?;
|
2021-12-03 18:13:20 +00:00
|
|
|
if post.ipfs_cid.is_some() {
|
|
|
|
return Err(HttpError::OperationError("post already saved to IPFS"));
|
|
|
|
};
|
2021-11-20 01:10:56 +00:00
|
|
|
if post.author.id != current_user.id || !post.is_public() {
|
|
|
|
// Users can only archive their own public posts
|
2021-12-03 18:13:20 +00:00
|
|
|
return Err(HttpError::PermissionError);
|
2021-11-20 01:10:56 +00:00
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
let ipfs_api_url = config.ipfs_api_url.as_ref()
|
|
|
|
.ok_or(HttpError::NotSupported)?;
|
|
|
|
|
|
|
|
let post_image_cid = if let Some(attachment) = post.attachments.first() {
|
|
|
|
// Add attachment to IPFS
|
|
|
|
let image_path = config.media_dir().join(&attachment.file_name);
|
|
|
|
let image_data = std::fs::read(image_path)
|
|
|
|
.map_err(|_| HttpError::InternalError)?;
|
2021-11-13 17:37:31 +00:00
|
|
|
let image_cid = ipfs_store::add(ipfs_api_url, image_data).await
|
2021-04-09 00:22:17 +00:00
|
|
|
.map_err(|_| HttpError::InternalError)?;
|
2021-09-28 21:44:31 +00:00
|
|
|
set_attachment_ipfs_cid(db_client, &attachment.id, &image_cid).await?;
|
2021-04-09 00:22:17 +00:00
|
|
|
image_cid
|
|
|
|
} else {
|
|
|
|
// Use IPFS logo if there's no image
|
|
|
|
IPFS_LOGO.to_string()
|
|
|
|
};
|
2022-01-11 18:15:20 +00:00
|
|
|
let post_url = post.get_object_id(&config.instance_url());
|
|
|
|
let post_metadata = PostMetadata::new(
|
2021-11-04 23:49:53 +00:00
|
|
|
&post.id,
|
2022-01-11 18:15:20 +00:00
|
|
|
&post_url,
|
|
|
|
&post.content,
|
2022-01-11 19:41:43 +00:00
|
|
|
&post.created_at,
|
2022-01-11 18:15:20 +00:00
|
|
|
&post_image_cid,
|
2021-11-04 23:49:53 +00:00
|
|
|
);
|
2021-04-09 00:22:17 +00:00
|
|
|
let post_metadata_json = serde_json::to_string(&post_metadata)
|
|
|
|
.map_err(|_| HttpError::InternalError)?
|
|
|
|
.as_bytes().to_vec();
|
2021-11-13 17:37:31 +00:00
|
|
|
let post_metadata_cid = ipfs_store::add(ipfs_api_url, post_metadata_json).await
|
2021-04-09 00:22:17 +00:00
|
|
|
.map_err(|_| HttpError::InternalError)?;
|
|
|
|
|
|
|
|
// Update post
|
|
|
|
post.ipfs_cid = Some(post_metadata_cid);
|
|
|
|
update_post(db_client, &post).await?;
|
2021-11-24 16:39:30 +00:00
|
|
|
get_reposted_posts(db_client, vec![&mut post]).await?;
|
2021-11-24 00:33:22 +00:00
|
|
|
get_actions_for_posts(db_client, ¤t_user.id, vec![&mut post]).await?;
|
2021-04-09 00:22:17 +00:00
|
|
|
let status = Status::from_post(post, &config.instance_url());
|
|
|
|
Ok(HttpResponse::Ok().json(status))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/{status_id}/signature")]
|
|
|
|
async fn get_signature(
|
2021-10-05 18:10:14 +00:00
|
|
|
auth: BearerAuth,
|
2021-04-09 00:22:17 +00:00
|
|
|
config: web::Data<Config>,
|
|
|
|
db_pool: web::Data<Pool>,
|
|
|
|
web::Path(status_id): web::Path<Uuid>,
|
|
|
|
) -> Result<HttpResponse, HttpError> {
|
|
|
|
let db_client = &**get_database_client(&db_pool).await?;
|
2021-10-05 18:10:14 +00:00
|
|
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
2022-01-25 22:17:28 +00:00
|
|
|
let blockchain_config = config.blockchain.as_ref()
|
2021-04-09 00:22:17 +00:00
|
|
|
.ok_or(HttpError::NotSupported)?;
|
2021-12-24 17:46:01 +00:00
|
|
|
let wallet_address = current_user.wallet_address
|
|
|
|
.ok_or(HttpError::PermissionError)?;
|
2021-04-09 00:22:17 +00:00
|
|
|
let post = get_post_by_id(db_client, &status_id).await?;
|
2021-11-20 01:10:56 +00:00
|
|
|
if post.author.id != current_user.id || !post.is_public() {
|
|
|
|
// Users can only tokenize their own public posts
|
2021-12-03 18:13:20 +00:00
|
|
|
return Err(HttpError::PermissionError);
|
2021-11-20 01:10:56 +00:00
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
let ipfs_cid = post.ipfs_cid
|
|
|
|
// Post metadata is not immutable
|
2022-01-23 23:12:58 +00:00
|
|
|
.ok_or(HttpError::PermissionError)?;
|
2021-04-09 00:22:17 +00:00
|
|
|
let token_uri = get_ipfs_url(&ipfs_cid);
|
|
|
|
let signature = create_mint_signature(
|
2022-01-25 22:17:28 +00:00
|
|
|
blockchain_config,
|
2021-12-24 17:46:01 +00:00
|
|
|
&wallet_address,
|
2021-04-09 00:22:17 +00:00
|
|
|
&token_uri,
|
|
|
|
).map_err(|_| HttpError::InternalError)?;
|
|
|
|
Ok(HttpResponse::Ok().json(signature))
|
|
|
|
}
|
|
|
|
|
2021-12-03 18:44:01 +00:00
|
|
|
#[post("/{status_id}/token_minted")]
|
|
|
|
async fn token_minted(
|
|
|
|
auth: BearerAuth,
|
|
|
|
config: web::Data<Config>,
|
|
|
|
db_pool: web::Data<Pool>,
|
|
|
|
web::Path(status_id): web::Path<Uuid>,
|
|
|
|
data: web::Json<TransactionData>,
|
|
|
|
) -> Result<HttpResponse, HttpError> {
|
|
|
|
let db_client = &**get_database_client(&db_pool).await?;
|
|
|
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
|
|
|
let mut post = get_post_by_id(db_client, &status_id).await?;
|
|
|
|
if post.token_tx_id.is_some() {
|
|
|
|
return Err(HttpError::OperationError("transaction is already registered"));
|
|
|
|
};
|
|
|
|
if post.author.id != current_user.id || !post.is_public() {
|
|
|
|
return Err(HttpError::PermissionError);
|
|
|
|
};
|
|
|
|
post.token_tx_id = Some(data.into_inner().transaction_id);
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
pub fn status_api_scope() -> Scope {
|
|
|
|
web::scope("/api/v1/statuses")
|
|
|
|
// Routes without status ID
|
|
|
|
.service(create_status)
|
|
|
|
// Routes with status ID
|
|
|
|
.service(get_status)
|
2021-12-04 00:42:36 +00:00
|
|
|
.service(delete_status)
|
2021-09-22 16:32:44 +00:00
|
|
|
.service(get_context)
|
2021-10-17 16:55:33 +00:00
|
|
|
.service(favourite)
|
2021-10-18 23:42:56 +00:00
|
|
|
.service(unfavourite)
|
2021-11-24 16:39:30 +00:00
|
|
|
.service(reblog)
|
|
|
|
.service(unreblog)
|
2021-04-09 00:22:17 +00:00
|
|
|
.service(make_permanent)
|
|
|
|
.service(get_signature)
|
2021-12-03 18:44:01 +00:00
|
|
|
.service(token_minted)
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|