293 lines
7.6 KiB
Rust
293 lines
7.6 KiB
Rust
use chrono::{DateTime, Utc};
|
|
use uuid::Uuid;
|
|
|
|
use fedimovies_utils::id::generate_ulid;
|
|
|
|
use crate::cleanup::{find_orphaned_files, DeletionQueue};
|
|
use crate::database::{catch_unique_violation, DatabaseClient, DatabaseError};
|
|
use crate::instances::queries::create_instance;
|
|
use crate::profiles::queries::update_emoji_caches;
|
|
|
|
use super::types::{DbEmoji, EmojiImage};
|
|
|
|
pub async fn create_emoji(
|
|
db_client: &impl DatabaseClient,
|
|
emoji_name: &str,
|
|
hostname: Option<&str>,
|
|
image: EmojiImage,
|
|
object_id: Option<&str>,
|
|
updated_at: &DateTime<Utc>,
|
|
) -> Result<DbEmoji, DatabaseError> {
|
|
let emoji_id = generate_ulid();
|
|
if let Some(hostname) = hostname {
|
|
create_instance(db_client, hostname).await?;
|
|
};
|
|
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 DatabaseClient,
|
|
emoji_id: &Uuid,
|
|
image: EmojiImage,
|
|
updated_at: &DateTime<Utc>,
|
|
) -> Result<DbEmoji, DatabaseError> {
|
|
let row = db_client
|
|
.query_one(
|
|
"
|
|
UPDATE emoji
|
|
SET
|
|
image = $1,
|
|
updated_at = $2
|
|
WHERE id = $3
|
|
RETURNING emoji
|
|
",
|
|
&[&image, &updated_at, &emoji_id],
|
|
)
|
|
.await?;
|
|
let emoji: DbEmoji = row.try_get("emoji")?;
|
|
update_emoji_caches(db_client, &emoji.id).await?;
|
|
Ok(emoji)
|
|
}
|
|
|
|
pub async fn get_local_emoji_by_name(
|
|
db_client: &impl DatabaseClient,
|
|
emoji_name: &str,
|
|
) -> Result<DbEmoji, DatabaseError> {
|
|
let maybe_row = db_client
|
|
.query_opt(
|
|
"
|
|
SELECT emoji
|
|
FROM emoji
|
|
WHERE hostname IS NULL AND emoji_name = $1
|
|
",
|
|
&[&emoji_name],
|
|
)
|
|
.await?;
|
|
let row = maybe_row.ok_or(DatabaseError::NotFound("emoji"))?;
|
|
let emoji = row.try_get("emoji")?;
|
|
Ok(emoji)
|
|
}
|
|
|
|
pub async fn get_local_emojis_by_names(
|
|
db_client: &impl DatabaseClient,
|
|
names: &[String],
|
|
) -> Result<Vec<DbEmoji>, DatabaseError> {
|
|
let rows = db_client
|
|
.query(
|
|
"
|
|
SELECT emoji
|
|
FROM emoji
|
|
WHERE hostname IS NULL AND emoji_name = ANY($1)
|
|
",
|
|
&[&names],
|
|
)
|
|
.await?;
|
|
let emojis = rows
|
|
.iter()
|
|
.map(|row| row.try_get("emoji"))
|
|
.collect::<Result<_, _>>()?;
|
|
Ok(emojis)
|
|
}
|
|
|
|
pub async fn get_local_emojis(
|
|
db_client: &impl DatabaseClient,
|
|
) -> Result<Vec<DbEmoji>, DatabaseError> {
|
|
let rows = db_client
|
|
.query(
|
|
"
|
|
SELECT emoji
|
|
FROM emoji
|
|
WHERE hostname IS NULL
|
|
",
|
|
&[],
|
|
)
|
|
.await?;
|
|
let emojis = rows
|
|
.iter()
|
|
.map(|row| row.try_get("emoji"))
|
|
.collect::<Result<_, _>>()?;
|
|
Ok(emojis)
|
|
}
|
|
|
|
pub async fn get_emoji_by_name_and_hostname(
|
|
db_client: &impl DatabaseClient,
|
|
emoji_name: &str,
|
|
hostname: &str,
|
|
) -> Result<DbEmoji, DatabaseError> {
|
|
let maybe_row = db_client
|
|
.query_opt(
|
|
"
|
|
SELECT emoji
|
|
FROM emoji WHERE emoji_name = $1 AND hostname = $2
|
|
",
|
|
&[&emoji_name, &hostname],
|
|
)
|
|
.await?;
|
|
let row = maybe_row.ok_or(DatabaseError::NotFound("emoji"))?;
|
|
let emoji = row.try_get("emoji")?;
|
|
Ok(emoji)
|
|
}
|
|
|
|
pub async fn get_emoji_by_remote_object_id(
|
|
db_client: &impl DatabaseClient,
|
|
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)
|
|
}
|
|
|
|
pub async fn delete_emoji(
|
|
db_client: &impl DatabaseClient,
|
|
emoji_id: &Uuid,
|
|
) -> Result<DeletionQueue, DatabaseError> {
|
|
let maybe_row = db_client
|
|
.query_opt(
|
|
"
|
|
DELETE FROM emoji WHERE id = $1
|
|
RETURNING emoji
|
|
",
|
|
&[&emoji_id],
|
|
)
|
|
.await?;
|
|
let row = maybe_row.ok_or(DatabaseError::NotFound("emoji"))?;
|
|
let emoji: DbEmoji = row.try_get("emoji")?;
|
|
update_emoji_caches(db_client, &emoji.id).await?;
|
|
let orphaned_files = find_orphaned_files(db_client, vec![emoji.image.file_name]).await?;
|
|
Ok(DeletionQueue {
|
|
files: orphaned_files,
|
|
ipfs_objects: vec![],
|
|
})
|
|
}
|
|
|
|
pub async fn find_unused_remote_emojis(
|
|
db_client: &impl DatabaseClient,
|
|
) -> Result<Vec<Uuid>, DatabaseError> {
|
|
let rows = db_client
|
|
.query(
|
|
"
|
|
SELECT emoji.id
|
|
FROM emoji
|
|
WHERE
|
|
emoji.object_id IS NOT NULL
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM post_emoji
|
|
WHERE post_emoji.emoji_id = emoji.id
|
|
)
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM profile_emoji
|
|
WHERE profile_emoji.emoji_id = emoji.id
|
|
)
|
|
",
|
|
&[],
|
|
)
|
|
.await?;
|
|
let ids: Vec<Uuid> = rows
|
|
.iter()
|
|
.map(|row| row.try_get("id"))
|
|
.collect::<Result<_, _>>()?;
|
|
Ok(ids)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::database::test_utils::create_test_database;
|
|
use serial_test::serial;
|
|
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn test_create_emoji() {
|
|
let db_client = &create_test_database().await;
|
|
let emoji_name = "test";
|
|
let hostname = "example.org";
|
|
let image = EmojiImage {
|
|
file_name: "test.png".to_string(),
|
|
file_size: 10000,
|
|
media_type: "image/png".to_string(),
|
|
};
|
|
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),
|
|
image,
|
|
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()));
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn test_update_emoji() {
|
|
let db_client = &create_test_database().await;
|
|
let image = EmojiImage::default();
|
|
let emoji = create_emoji(db_client, "test", None, image.clone(), None, &Utc::now())
|
|
.await
|
|
.unwrap();
|
|
let updated_emoji = update_emoji(db_client, &emoji.id, image, &Utc::now())
|
|
.await
|
|
.unwrap();
|
|
assert_ne!(updated_emoji.updated_at, emoji.updated_at);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn test_delete_emoji() {
|
|
let db_client = &create_test_database().await;
|
|
let image = EmojiImage::default();
|
|
let emoji = create_emoji(db_client, "test", None, image, None, &Utc::now())
|
|
.await
|
|
.unwrap();
|
|
let deletion_queue = delete_emoji(db_client, &emoji.id).await.unwrap();
|
|
assert_eq!(deletion_queue.files.len(), 1);
|
|
assert_eq!(deletion_queue.ipfs_objects.len(), 0);
|
|
}
|
|
}
|