add: custom emojis

fix: don't show reposts of posts from blocked users
fix: don't show questions when they're from users you've blocked
This commit is contained in:
trisua 2025-05-10 21:58:02 -04:00
parent 9f187039e6
commit 275dd0a1eb
25 changed files with 697 additions and 61 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "tetratto-core"
version = "2.3.0"
version = "3.0.0"
edition = "2024"
[features]

View file

@ -1,3 +1,5 @@
use std::collections::HashMap;
use super::*;
use crate::cache::Cache;
use crate::model::{
@ -55,6 +57,29 @@ impl DataManager {
Ok(res.unwrap())
}
/// Get all emojis by their community for the communities the given user is in.
pub async fn get_user_emojis(
&self,
id: usize,
) -> Result<HashMap<usize, (String, Vec<CustomEmoji>)>> {
let memberships = self.get_memberships_by_owner(id).await?;
let mut out = HashMap::new();
for membership in memberships {
let community = self.get_community_by_id(membership.community).await?;
out.insert(
community.id,
(
community.title.clone(),
self.get_emojis_by_community(community.id).await?,
),
);
}
Ok(out)
}
/// Get an emoji by community and name.
///
/// # Arguments
@ -134,8 +159,8 @@ impl DataManager {
"INSERT INTO emojis VALUES ($1, $2, $3, $4, $5, $6, $7)",
params![
&(data.id as i64),
&(data.owner as i64),
&(data.created as i64),
&(data.owner as i64),
&(data.community as i64),
&(data.upload_id as i64),
&data.name,
@ -176,7 +201,7 @@ impl DataManager {
return Err(Error::DatabaseError(e.to_string()));
}
// delete uploads
// delete upload
self.delete_upload(emoji.upload_id).await?;
// ...
@ -184,5 +209,5 @@ impl DataManager {
Ok(())
}
auto_method!(update_emoji_name(&str)@get_emoji_by_id:MANAGE_EMOJIS -> "UPDATE emojis SET title = $1 WHERE id = $2" --cache-key-tmpl="atto.emoji:{}");
auto_method!(update_emoji_name(&str)@get_emoji_by_id:MANAGE_EMOJIS -> "UPDATE emojis SET name = $1 WHERE id = $2" --cache-key-tmpl="atto.emoji:{}");
}

View file

@ -78,7 +78,11 @@ impl DataManager {
}
/// Get the post the given post is reposting (if some).
pub async fn get_post_reposting(&self, post: &Post) -> Option<(User, Post)> {
pub async fn get_post_reposting(
&self,
post: &Post,
ignore_users: &[usize],
) -> Option<(User, Post)> {
if let Some(ref repost) = post.context.repost {
if let Some(reposting) = repost.reposting {
let mut x = match self.get_post_by_id(reposting).await {
@ -86,6 +90,10 @@ impl DataManager {
Err(_) => return None,
};
if ignore_users.contains(&x.owner) {
return None;
}
x.mark_as_repost();
Some((
match self.get_user_by_id(x.owner).await {
@ -103,9 +111,18 @@ impl DataManager {
}
/// Get the question of a given post.
pub async fn get_post_question(&self, post: &Post) -> Result<Option<(Question, User)>> {
pub async fn get_post_question(
&self,
post: &Post,
ignore_users: &[usize],
) -> Result<Option<(Question, User)>> {
if post.context.answering != 0 {
let question = self.get_question_by_id(post.context.answering).await?;
if ignore_users.contains(&question.owner) {
return Ok(None);
}
let user = if question.owner == 0 {
User::anonymous()
} else {
@ -138,8 +155,8 @@ impl DataManager {
out.push((
post.clone(),
user.clone(),
self.get_post_reposting(&post).await,
self.get_post_question(&post).await?,
self.get_post_reposting(&post, ignore_users).await,
self.get_post_question(&post, ignore_users).await?,
));
} else {
let user = self.get_user_by_id(owner).await?;
@ -147,8 +164,8 @@ impl DataManager {
out.push((
post.clone(),
user,
self.get_post_reposting(&post).await,
self.get_post_question(&post).await?,
self.get_post_reposting(&post, ignore_users).await,
self.get_post_question(&post, ignore_users).await?,
));
}
}
@ -196,8 +213,8 @@ impl DataManager {
post.clone(),
user.clone(),
community.to_owned(),
self.get_post_reposting(&post).await,
self.get_post_question(&post).await?,
self.get_post_reposting(&post, ignore_users).await,
self.get_post_question(&post, ignore_users).await?,
));
} else {
let user = self.get_user_by_id(owner).await?;
@ -235,8 +252,8 @@ impl DataManager {
post.clone(),
user,
community,
self.get_post_reposting(&post).await,
self.get_post_question(&post).await?,
self.get_post_reposting(&post, ignore_users).await,
self.get_post_question(&post, ignore_users).await?,
));
}
}

View file

@ -1,10 +1,8 @@
use std::fs::{exists, remove_file};
use super::*;
use crate::cache::Cache;
use crate::model::{Error, Result, uploads::MediaUpload};
use crate::{auto_method, execute, get, query_row, query_rows, params};
use pathbufd::PathBufD;
#[cfg(feature = "sqlite")]
use rusqlite::Row;
@ -56,7 +54,7 @@ impl DataManager {
///
/// # Arguments
/// * `data` - a mock [`MediaUpload`] object to insert
pub async fn create_upload(&self, data: MediaUpload) -> Result<()> {
pub async fn create_upload(&self, data: MediaUpload) -> Result<MediaUpload> {
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
@ -78,7 +76,7 @@ impl DataManager {
}
// return
Ok(())
Ok(data)
}
pub async fn delete_upload(&self, id: usize) -> Result<()> {
@ -91,16 +89,8 @@ impl DataManager {
// if there's an issue in the database
//
// the actual file takes up much more space than the database entry.
let path = PathBufD::current().extend(&[self.0.dirs.media.as_str(), "uploads"]);
if let Ok(exists) = exists(&path) {
if exists {
if let Err(e) = remove_file(&path) {
return Err(Error::MiscError(e.to_string()));
}
}
} else {
return Err(Error::GeneralNotFound("file".to_string()));
}
let upload = self.get_upload_by_id(id).await?;
upload.remove(&self.0)?;
// delete from database
let conn = match self.connect().await {

View file

@ -1,5 +1,9 @@
use pathbufd::PathBufD;
use serde::{Serialize, Deserialize};
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
use crate::config::Config;
use std::fs::{write, exists, remove_file};
use super::{Error, Result};
#[derive(Serialize, Deserialize)]
pub enum MediaType {
@ -15,6 +19,22 @@ pub enum MediaType {
Gif,
}
impl MediaType {
pub fn extension(&self) -> &str {
match self {
Self::Webp => "webp",
Self::Avif => "avif",
Self::Png => "png",
Self::Jpg => "jpg",
Self::Gif => "gif",
}
}
pub fn mime(&self) -> String {
format!("image/{}", self.extension())
}
}
#[derive(Serialize, Deserialize)]
pub struct MediaUpload {
pub id: usize,
@ -33,6 +53,35 @@ impl MediaUpload {
what,
}
}
/// Get the path to the fs file for this upload.
pub fn path(&self, config: &Config) -> PathBufD {
PathBufD::current()
.extend(&[config.dirs.media.as_str(), "uploads"])
.join(format!("{}.{}", self.id, self.what.extension()))
}
/// Write to this upload in the file system.
pub fn write(&self, config: &Config, bytes: &[u8]) -> Result<()> {
match write(self.path(config), bytes) {
Ok(_) => Ok(()),
Err(e) => Err(Error::MiscError(e.to_string())),
}
}
/// Delete this upload in the file system.
pub fn remove(&self, config: &Config) -> Result<()> {
let path = self.path(config);
if !exists(&path).unwrap() {
return Ok(());
}
match remove_file(path) {
Ok(_) => Ok(()),
Err(e) => Err(Error::MiscError(e.to_string())),
}
}
}
#[derive(Serialize, Deserialize)]
@ -86,7 +135,7 @@ impl CustomEmoji {
out = out.replace(
&emoji.0,
&format!(
"<img class=\"emoji\" src=\"/api/v1/communities/{}/emoji/{}\" />",
"<img class=\"emoji\" src=\"/api/v1/communities/{}/emojis/{}\" />",
emoji.1, emoji.2
),
);