Download custom emojis contained in remote posts

This commit is contained in:
silverpill 2023-01-07 19:28:26 +00:00
parent 7b8a56dd8f
commit 56e75895bd
14 changed files with 366 additions and 25 deletions

View file

@ -0,0 +1,16 @@
CREATE TABLE emoji (
id UUID PRIMARY KEY,
emoji_name VARCHAR(100) NOT NULL,
hostname VARCHAR(100) REFERENCES instance (hostname) ON DELETE RESTRICT,
image JSONB NOT NULL,
object_id VARCHAR(250) UNIQUE,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
UNIQUE (emoji_name, hostname),
CHECK ((hostname IS NULL) = (object_id IS NULL))
);
CREATE TABLE post_emoji (
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
PRIMARY KEY (post_id, emoji_id)
);

View file

@ -136,6 +136,23 @@ CREATE TABLE post_link (
PRIMARY KEY (source_id, target_id) PRIMARY KEY (source_id, target_id)
); );
CREATE TABLE emoji (
id UUID PRIMARY KEY,
emoji_name VARCHAR(100) NOT NULL,
hostname VARCHAR(100) REFERENCES instance (hostname) ON DELETE RESTRICT,
image JSONB NOT NULL,
object_id VARCHAR(250) UNIQUE,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
UNIQUE (emoji_name, hostname),
CHECK ((hostname IS NULL) = (object_id IS NULL))
);
CREATE TABLE post_emoji (
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
PRIMARY KEY (post_id, emoji_id)
);
CREATE TABLE notification ( CREATE TABLE notification (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
sender_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE, sender_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,

View file

@ -23,6 +23,11 @@ use crate::config::{Config, Instance};
use crate::database::DatabaseError; use crate::database::DatabaseError;
use crate::errors::{ConversionError, ValidationError}; use crate::errors::{ConversionError, ValidationError};
use crate::models::attachments::queries::create_attachment; use crate::models::attachments::queries::create_attachment;
use crate::models::emojis::queries::{
create_emoji,
get_emoji_by_remote_object_id,
update_emoji,
};
use crate::models::posts::{ use crate::models::posts::{
hashtags::normalize_hashtag, hashtags::normalize_hashtag,
helpers::get_post_by_object_id, helpers::get_post_by_object_id,
@ -33,11 +38,17 @@ use crate::models::posts::{
content_allowed_classes, content_allowed_classes,
ATTACHMENTS_MAX_NUM, ATTACHMENTS_MAX_NUM,
CONTENT_MAX_SIZE, CONTENT_MAX_SIZE,
EMOJI_MAX_SIZE,
EMOJI_MEDIA_TYPE,
EMOJIS_MAX_NUM,
}, },
}; };
use crate::models::profiles::types::DbActorProfile; use crate::models::profiles::types::DbActorProfile;
use crate::models::users::queries::get_user_by_name; use crate::models::users::queries::get_user_by_name;
use crate::utils::html::clean_html; use crate::utils::{
html::clean_html,
urls::get_hostname,
};
use super::HandlerResult; use super::HandlerResult;
fn get_note_author_id(object: &Object) -> Result<String, ValidationError> { fn get_note_author_id(object: &Object) -> Result<String, ValidationError> {
@ -221,6 +232,7 @@ pub async fn handle_note(
let mut mentions: Vec<Uuid> = Vec::new(); let mut mentions: Vec<Uuid> = Vec::new();
let mut hashtags = vec![]; let mut hashtags = vec![];
let mut links = vec![]; let mut links = vec![];
let mut emojis = vec![];
if let Some(value) = object.tag { if let Some(value) = object.tag {
let list: Vec<JsonValue> = parse_property_value(&value) let list: Vec<JsonValue> = parse_property_value(&value)
.map_err(|_| ValidationError("invalid tag property"))?; .map_err(|_| ValidationError("invalid tag property"))?;
@ -343,13 +355,84 @@ pub async fn handle_note(
links.push(linked.id); links.push(linked.id);
}; };
} else if tag_type == EMOJI { } else if tag_type == EMOJI {
let _tag: EmojiTag = match serde_json::from_value(tag_value.clone()) { let tag: EmojiTag = match serde_json::from_value(tag_value) {
Ok(tag) => tag, Ok(tag) => tag,
Err(_) => { Err(_) => {
log::warn!("invalid emoji tag"); log::warn!("invalid emoji tag");
continue; continue;
}, },
}; };
if emojis.len() >= EMOJIS_MAX_NUM {
log::warn!("too many emojis");
continue;
};
let tag_name = tag.name.trim_matches(':');
let maybe_emoji_id = match get_emoji_by_remote_object_id(
db_client,
&tag.id,
).await {
Ok(emoji) => {
if emoji.updated_at >= tag.updated {
// Emoji already exists and is up to date
if !emojis.contains(&emoji.id) {
emojis.push(emoji.id);
};
continue;
};
if emoji.emoji_name != tag_name {
log::warn!("emoji name can't be changed");
continue;
};
Some(emoji.id)
},
Err(DatabaseError::NotFound("emoji")) => None,
Err(other_error) => return Err(other_error.into()),
};
let (file_name, maybe_media_type) = match fetch_file(
instance,
&tag.icon.url,
tag.icon.media_type.as_deref(),
EMOJI_MAX_SIZE,
media_dir,
).await {
Ok(file) => file,
Err(error) => {
log::warn!("failed to fetch emoji: {}", error);
continue;
},
};
let media_type = match maybe_media_type.as_deref() {
Some(media_type @ EMOJI_MEDIA_TYPE) => media_type,
_ => {
log::warn!("unexpected emoji media type: {:?}", maybe_media_type);
continue;
},
};
log::info!("downloaded emoji {}", tag.icon.url);
let emoji = if let Some(emoji_id) = maybe_emoji_id {
update_emoji(
db_client,
&emoji_id,
&file_name,
media_type,
&tag.updated,
).await?
} else {
let hostname = get_hostname(&tag.id)
.map_err(|_| ValidationError("invalid emoji ID"))?;
create_emoji(
db_client,
tag_name,
Some(&hostname),
&file_name,
media_type,
Some(&tag.id),
&tag.updated,
).await?
};
if !emojis.contains(&emoji.id) {
emojis.push(emoji.id);
};
} else { } else {
log::warn!( log::warn!(
"skipping tag of type {}", "skipping tag of type {}",
@ -417,6 +500,7 @@ pub async fn handle_note(
mentions: mentions, mentions: mentions,
tags: hashtags, tags: hashtags,
links: links, links: links,
emojis: emojis,
object_id: Some(object.id), object_id: Some(object.id),
created_at, created_at,
}; };

View file

@ -57,11 +57,11 @@ pub struct LinkTag {
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct EmojiImage { pub struct EmojiImage {
#[serde(rename = "type")] #[serde(rename = "type")]
object_type: String, object_type: String,
url: String, pub url: String,
media_type: Option<String>, pub media_type: Option<String>,
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -70,10 +70,10 @@ struct EmojiImage {
pub struct EmojiTag { pub struct EmojiTag {
#[serde(rename = "type")] #[serde(rename = "type")]
tag_type: String, tag_type: String,
icon: EmojiImage, pub icon: EmojiImage,
id: String, pub id: String,
name: String, pub name: String,
updated: DateTime<Utc>, pub updated: DateTime<Utc>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]

View file

@ -155,6 +155,7 @@ async fn create_status(
mentions: mentions, mentions: mentions,
tags: tags, tags: tags,
links: links, links: links,
emojis: vec![],
object_id: None, object_id: None,
created_at: Utc::now(), created_at: Utc::now(),
}; };

2
src/models/emojis/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod queries;
pub mod types;

View file

@ -0,0 +1,135 @@
use chrono::{DateTime, Utc};
use tokio_postgres::GenericClient;
use uuid::Uuid;
use crate::database::{catch_unique_violation, DatabaseError};
use crate::models::{
instances::queries::create_instance,
profiles::types::ProfileImage,
};
use crate::utils::id::new_uuid;
use super::types::DbEmoji;
pub async fn create_emoji(
db_client: &impl GenericClient,
emoji_name: &str,
hostname: Option<&str>,
file_name: &str,
media_type: &str,
object_id: Option<&str>,
updated_at: &DateTime<Utc>,
) -> Result<DbEmoji, DatabaseError> {
let emoji_id = new_uuid();
if let Some(hostname) = hostname {
create_instance(db_client, hostname).await?;
};
let image = ProfileImage {
file_name: file_name.to_string(),
media_type: Some(media_type.to_string()),
};
let row = db_client.query_one(
"
INSERT INTO emoji (
id,
emoji_name,
hostname,
image,
object_id,
updated_at
)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING emoji
",
&[
&emoji_id,
&emoji_name,
&hostname,
&image,
&object_id,
&updated_at,
],
).await.map_err(catch_unique_violation("emoji"))?;
let emoji = row.try_get("emoji")?;
Ok(emoji)
}
pub async fn update_emoji(
db_client: &impl GenericClient,
emoji_id: &Uuid,
file_name: &str,
media_type: &str,
updated_at: &DateTime<Utc>,
) -> Result<DbEmoji, DatabaseError> {
let image = ProfileImage {
file_name: file_name.to_string(),
media_type: Some(media_type.to_string()),
};
let row = db_client.query_one(
"
UPDATE emoji
SET
image = $1,
updated_at = $2
WHERE id = $4
RETURNING emoji
",
&[
&image,
&updated_at,
&emoji_id,
],
).await?;
let emoji = row.try_get("emoji")?;
Ok(emoji)
}
pub async fn get_emoji_by_remote_object_id(
db_client: &impl GenericClient,
object_id: &str,
) -> Result<DbEmoji, DatabaseError> {
let maybe_row = db_client.query_opt(
"
SELECT emoji
FROM emoji WHERE object_id = $1
",
&[&object_id],
).await?;
let row = maybe_row.ok_or(DatabaseError::NotFound("emoji"))?;
let emoji = row.try_get("emoji")?;
Ok(emoji)
}
#[cfg(test)]
mod tests {
use serial_test::serial;
use crate::database::test_utils::create_test_database;
use super::*;
#[tokio::test]
#[serial]
async fn test_create_emoji() {
let db_client = &create_test_database().await;
let emoji_name = "test";
let hostname = "example.org";
let file_name = "test.png";
let media_type = "image/png";
let object_id = "https://example.org/emojis/test";
let updated_at = Utc::now();
let DbEmoji { id: emoji_id, .. } = create_emoji(
db_client,
emoji_name,
Some(hostname),
file_name,
media_type,
Some(object_id),
&updated_at,
).await.unwrap();
let emoji = get_emoji_by_remote_object_id(
db_client,
object_id,
).await.unwrap();
assert_eq!(emoji.id, emoji_id);
assert_eq!(emoji.emoji_name, emoji_name);
assert_eq!(emoji.hostname, Some(hostname.to_string()));
}
}

View file

@ -0,0 +1,16 @@
use chrono::{DateTime, Utc};
use postgres_types::FromSql;
use uuid::Uuid;
use crate::models::profiles::types::ProfileImage;
#[derive(Clone, FromSql)]
#[postgres(name = "emoji")]
pub struct DbEmoji {
pub id: Uuid,
pub emoji_name: String,
pub hostname: Option<String>,
pub image: ProfileImage,
pub object_id: Option<String>,
pub updated_at: DateTime<Utc>,
}

View file

@ -1,6 +1,7 @@
pub mod attachments; pub mod attachments;
pub mod background_jobs; pub mod background_jobs;
pub mod cleanup; pub mod cleanup;
pub mod emojis;
pub mod instances; pub mod instances;
pub mod invoices; pub mod invoices;
pub mod markers; pub mod markers;

View file

@ -5,6 +5,7 @@ use crate::database::DatabaseError;
use crate::models::posts::helpers::{add_related_posts, add_user_actions}; use crate::models::posts::helpers::{add_related_posts, add_user_actions};
use crate::models::posts::queries::{ use crate::models::posts::queries::{
RELATED_ATTACHMENTS, RELATED_ATTACHMENTS,
RELATED_EMOJIS,
RELATED_LINKS, RELATED_LINKS,
RELATED_MENTIONS, RELATED_MENTIONS,
RELATED_TAGS, RELATED_TAGS,
@ -138,7 +139,8 @@ pub async fn get_notifications(
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags}, {related_tags},
{related_links} {related_links},
{related_emojis}
FROM notification FROM notification
JOIN actor_profile AS sender JOIN actor_profile AS sender
ON notification.sender_id = sender.id ON notification.sender_id = sender.id
@ -156,6 +158,7 @@ pub async fn get_notifications(
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS, related_links=RELATED_LINKS,
related_emojis=RELATED_EMOJIS,
); );
let rows = db_client.query( let rows = db_client.query(
&statement, &statement,

View file

@ -8,9 +8,12 @@ use crate::database::{
DatabaseError, DatabaseError,
DatabaseTypeError, DatabaseTypeError,
}; };
use crate::models::attachments::types::DbMediaAttachment; use crate::models::{
use crate::models::posts::types::{DbPost, Post}; attachments::types::DbMediaAttachment,
use crate::models::profiles::types::DbActorProfile; emojis::types::DbEmoji,
posts::types::{DbPost, Post},
profiles::types::DbActorProfile,
};
#[derive(Debug)] #[derive(Debug)]
pub enum EventType { pub enum EventType {
@ -102,6 +105,7 @@ impl TryFrom<&Row> for Notification {
let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?; let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?;
let db_tags: Vec<String> = row.try_get("tags")?; let db_tags: Vec<String> = row.try_get("tags")?;
let db_links: Vec<Uuid> = row.try_get("links")?; let db_links: Vec<Uuid> = row.try_get("links")?;
let db_emojis: Vec<DbEmoji> = row.try_get("emojis")?;
let post = Post::new( let post = Post::new(
db_post, db_post,
db_post_author, db_post_author,
@ -109,6 +113,7 @@ impl TryFrom<&Row> for Notification {
db_mentions, db_mentions,
db_tags, db_tags,
db_links, db_links,
db_emojis,
)?; )?;
Some(post) Some(post)
}, },

View file

@ -14,6 +14,7 @@ use crate::models::cleanup::{
find_orphaned_ipfs_objects, find_orphaned_ipfs_objects,
DeletionQueue, DeletionQueue,
}; };
use crate::models::emojis::types::DbEmoji;
use crate::models::notifications::queries::{ use crate::models::notifications::queries::{
create_mention_notification, create_mention_notification,
create_reply_notification, create_reply_notification,
@ -157,6 +158,24 @@ pub async fn create_post(
let db_links: Vec<Uuid> = links_rows.iter() let db_links: Vec<Uuid> = links_rows.iter()
.map(|row| row.try_get("target_id")) .map(|row| row.try_get("target_id"))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
// Create emojis
let emojis_rows = transaction.query(
"
INSERT INTO post_emoji (post_id, emoji_id)
SELECT $1, emoji.id FROM emoji WHERE id = ANY($2)
RETURNING (
SELECT emoji FROM emoji
WHERE emoji.id = emoji_id
)
",
&[&db_post.id, &data.emojis],
).await?;
if emojis_rows.len() != data.emojis.len() {
return Err(DatabaseError::NotFound("emoji"));
};
let db_emojis: Vec<DbEmoji> = emojis_rows.iter()
.map(|row| row.try_get("emoji"))
.collect::<Result<_, _>>()?;
// Update counters // Update counters
let author = update_post_count(&transaction, &db_post.author_id, 1).await?; let author = update_post_count(&transaction, &db_post.author_id, 1).await?;
let mut notified_users = vec![]; let mut notified_users = vec![];
@ -216,6 +235,7 @@ pub async fn create_post(
db_mentions, db_mentions,
db_tags, db_tags,
db_links, db_links,
db_emojis,
)?; )?;
Ok(post) Ok(post)
} }
@ -247,6 +267,14 @@ pub const RELATED_LINKS: &str =
WHERE post_link.source_id = post.id WHERE post_link.source_id = post.id
) AS links"; ) AS links";
pub const RELATED_EMOJIS: &str =
"ARRAY(
SELECT emoji
FROM post_emoji
JOIN emoji ON post_emoji.emoji_id = emoji.id
WHERE post_emoji.post_id = post.id
) AS emojis";
fn build_visibility_filter() -> String { fn build_visibility_filter() -> String {
format!( format!(
"( "(
@ -287,7 +315,8 @@ pub async fn get_home_timeline(
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags}, {related_tags},
{related_links} {related_links},
{related_emojis}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE WHERE
@ -353,6 +382,7 @@ pub async fn get_home_timeline(
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS, related_links=RELATED_LINKS,
related_emojis=RELATED_EMOJIS,
relationship_follow=i16::from(&RelationshipType::Follow), relationship_follow=i16::from(&RelationshipType::Follow),
relationship_subscription=i16::from(&RelationshipType::Subscription), relationship_subscription=i16::from(&RelationshipType::Subscription),
relationship_hide_reposts=i16::from(&RelationshipType::HideReposts), relationship_hide_reposts=i16::from(&RelationshipType::HideReposts),
@ -386,7 +416,8 @@ pub async fn get_local_timeline(
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags}, {related_tags},
{related_links} {related_links},
{related_emojis}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE WHERE
@ -400,6 +431,7 @@ pub async fn get_local_timeline(
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS, related_links=RELATED_LINKS,
related_emojis=RELATED_EMOJIS,
visibility_public=i16::from(&Visibility::Public), visibility_public=i16::from(&Visibility::Public),
); );
let limit: i64 = limit.into(); let limit: i64 = limit.into();
@ -427,7 +459,8 @@ pub async fn get_related_posts(
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags}, {related_tags},
{related_links} {related_links},
{related_emojis}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE post.id IN ( WHERE post.id IN (
@ -449,6 +482,7 @@ pub async fn get_related_posts(
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS, related_links=RELATED_LINKS,
related_emojis=RELATED_EMOJIS,
); );
let rows = db_client.query( let rows = db_client.query(
&statement, &statement,
@ -488,7 +522,8 @@ pub async fn get_posts_by_author(
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags}, {related_tags},
{related_links} {related_links},
{related_emojis}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE {condition} WHERE {condition}
@ -499,6 +534,7 @@ pub async fn get_posts_by_author(
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS, related_links=RELATED_LINKS,
related_emojis=RELATED_EMOJIS,
condition=condition, condition=condition,
); );
let limit: i64 = limit.into(); let limit: i64 = limit.into();
@ -531,7 +567,8 @@ pub async fn get_posts_by_tag(
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags}, {related_tags},
{related_links} {related_links},
{related_emojis}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE WHERE
@ -548,6 +585,7 @@ pub async fn get_posts_by_tag(
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS, related_links=RELATED_LINKS,
related_emojis=RELATED_EMOJIS,
visibility_filter=build_visibility_filter(), visibility_filter=build_visibility_filter(),
); );
let limit: i64 = limit.into(); let limit: i64 = limit.into();
@ -576,7 +614,8 @@ pub async fn get_post_by_id(
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags}, {related_tags},
{related_links} {related_links},
{related_emojis}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE post.id = $1 WHERE post.id = $1
@ -585,6 +624,7 @@ pub async fn get_post_by_id(
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS, related_links=RELATED_LINKS,
related_emojis=RELATED_EMOJIS,
); );
let maybe_row = db_client.query_opt( let maybe_row = db_client.query_opt(
&statement, &statement,
@ -629,7 +669,8 @@ pub async fn get_thread(
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags}, {related_tags},
{related_links} {related_links},
{related_emojis}
FROM post FROM post
JOIN thread ON post.id = thread.id JOIN thread ON post.id = thread.id
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
@ -640,6 +681,7 @@ pub async fn get_thread(
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS, related_links=RELATED_LINKS,
related_emojis=RELATED_EMOJIS,
visibility_filter=build_visibility_filter(), visibility_filter=build_visibility_filter(),
); );
let query = query!( let query = query!(
@ -668,7 +710,8 @@ pub async fn get_post_by_remote_object_id(
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags}, {related_tags},
{related_links} {related_links},
{related_emojis}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE post.object_id = $1 WHERE post.object_id = $1
@ -677,6 +720,7 @@ pub async fn get_post_by_remote_object_id(
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS, related_links=RELATED_LINKS,
related_emojis=RELATED_EMOJIS,
); );
let maybe_row = db_client.query_opt( let maybe_row = db_client.query_opt(
&statement, &statement,
@ -698,7 +742,8 @@ pub async fn get_post_by_ipfs_cid(
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags}, {related_tags},
{related_links} {related_links},
{related_emojis}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE post.ipfs_cid = $1 WHERE post.ipfs_cid = $1
@ -707,6 +752,7 @@ pub async fn get_post_by_ipfs_cid(
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS, related_links=RELATED_LINKS,
related_emojis=RELATED_EMOJIS,
); );
let result = db_client.query_opt( let result = db_client.query_opt(
&statement, &statement,

View file

@ -9,8 +9,11 @@ use crate::database::{
DatabaseError, DatabaseError,
DatabaseTypeError, DatabaseTypeError,
}; };
use crate::models::attachments::types::DbMediaAttachment; use crate::models::{
use crate::models::profiles::types::DbActorProfile; attachments::types::DbMediaAttachment,
emojis::types::DbEmoji,
profiles::types::DbActorProfile,
};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Visibility { pub enum Visibility {
@ -95,6 +98,7 @@ pub struct Post {
pub mentions: Vec<DbActorProfile>, pub mentions: Vec<DbActorProfile>,
pub tags: Vec<String>, pub tags: Vec<String>,
pub links: Vec<Uuid>, pub links: Vec<Uuid>,
pub emojis: Vec<DbEmoji>,
pub object_id: Option<String>, pub object_id: Option<String>,
pub ipfs_cid: Option<String>, pub ipfs_cid: Option<String>,
pub token_id: Option<i32>, pub token_id: Option<i32>,
@ -118,6 +122,7 @@ impl Post {
db_mentions: Vec<DbActorProfile>, db_mentions: Vec<DbActorProfile>,
db_tags: Vec<String>, db_tags: Vec<String>,
db_links: Vec<Uuid>, db_links: Vec<Uuid>,
db_emojis: Vec<DbEmoji>,
) -> Result<Self, DatabaseTypeError> { ) -> Result<Self, DatabaseTypeError> {
// Consistency checks // Consistency checks
if db_post.author_id != db_author.id { if db_post.author_id != db_author.id {
@ -135,7 +140,8 @@ impl Post {
!db_attachments.is_empty() || !db_attachments.is_empty() ||
!db_mentions.is_empty() || !db_mentions.is_empty() ||
!db_tags.is_empty() || !db_tags.is_empty() ||
!db_links.is_empty() !db_links.is_empty() ||
!db_emojis.is_empty()
) { ) {
return Err(DatabaseTypeError); return Err(DatabaseTypeError);
}; };
@ -153,6 +159,7 @@ impl Post {
mentions: db_mentions, mentions: db_mentions,
tags: db_tags, tags: db_tags,
links: db_links, links: db_links,
emojis: db_emojis,
object_id: db_post.object_id, object_id: db_post.object_id,
ipfs_cid: db_post.ipfs_cid, ipfs_cid: db_post.ipfs_cid,
token_id: db_post.token_id, token_id: db_post.token_id,
@ -200,6 +207,7 @@ impl Default for Post {
mentions: vec![], mentions: vec![],
tags: vec![], tags: vec![],
links: vec![], links: vec![],
emojis: vec![],
object_id: None, object_id: None,
ipfs_cid: None, ipfs_cid: None,
token_id: None, token_id: None,
@ -225,6 +233,7 @@ impl TryFrom<&Row> for Post {
let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?; let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?;
let db_tags: Vec<String> = row.try_get("tags")?; let db_tags: Vec<String> = row.try_get("tags")?;
let db_links: Vec<Uuid> = row.try_get("links")?; let db_links: Vec<Uuid> = row.try_get("links")?;
let db_emojis: Vec<DbEmoji> = row.try_get("emojis")?;
let post = Self::new( let post = Self::new(
db_post, db_post,
db_profile, db_profile,
@ -232,6 +241,7 @@ impl TryFrom<&Row> for Post {
db_mentions, db_mentions,
db_tags, db_tags,
db_links, db_links,
db_emojis,
)?; )?;
Ok(post) Ok(post)
} }
@ -247,6 +257,7 @@ pub struct PostCreateData {
pub mentions: Vec<Uuid>, pub mentions: Vec<Uuid>,
pub tags: Vec<String>, pub tags: Vec<String>,
pub links: Vec<Uuid>, pub links: Vec<Uuid>,
pub emojis: Vec<Uuid>,
pub object_id: Option<String>, pub object_id: Option<String>,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
} }

View file

@ -2,6 +2,10 @@ use crate::errors::ValidationError;
use crate::utils::html::clean_html_strict; use crate::utils::html::clean_html_strict;
pub const ATTACHMENTS_MAX_NUM: usize = 15; pub const ATTACHMENTS_MAX_NUM: usize = 15;
pub const EMOJI_MAX_SIZE: u64 = 100 * 1000; // 100 kB
pub const EMOJI_MEDIA_TYPE: &str = "image/png";
pub const EMOJIS_MAX_NUM: usize = 20;
pub const CONTENT_MAX_SIZE: usize = 100000; pub const CONTENT_MAX_SIZE: usize = 100000;
const CONTENT_ALLOWED_TAGS: [&str; 8] = [ const CONTENT_ALLOWED_TAGS: [&str; 8] = [
"a", "a",