Download custom emojis contained in remote posts
This commit is contained in:
parent
7b8a56dd8f
commit
56e75895bd
14 changed files with 366 additions and 25 deletions
16
migrations/V0039__emoji.sql
Normal file
16
migrations/V0039__emoji.sql
Normal 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)
|
||||||
|
);
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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
2
src/models/emojis/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod queries;
|
||||||
|
pub mod types;
|
135
src/models/emojis/queries.rs
Normal file
135
src/models/emojis/queries.rs
Normal 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()));
|
||||||
|
}
|
||||||
|
}
|
16
src/models/emojis/types.rs
Normal file
16
src/models/emojis/types.rs
Normal 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>,
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue