tetratto_core/database/
emojis.rs

1use std::collections::HashMap;
2
3use oiseau::cache::Cache;
4use crate::model::{
5    Error, Result, auth::User, permissions::FinePermission,
6    communities_permissions::CommunityPermission, uploads::CustomEmoji,
7};
8use crate::{auto_method, DataManager};
9
10use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};
11
12impl DataManager {
13    /// Get a [`CustomEmoji`] from an SQL row.
14    pub(crate) fn get_emoji_from_row(x: &PostgresRow) -> CustomEmoji {
15        CustomEmoji {
16            id: get!(x->0(i64)) as usize,
17            owner: get!(x->1(i64)) as usize,
18            created: get!(x->2(i64)) as usize,
19            community: get!(x->3(i64)) as usize,
20            upload_id: get!(x->4(i64)) as usize,
21            name: get!(x->5(String)),
22            is_animated: get!(x->6(i32)) as i8 == 1,
23        }
24    }
25
26    auto_method!(get_emoji_by_id(usize as i64)@get_emoji_from_row -> "SELECT * FROM emojis WHERE id = $1" --name="emoji" --returns=CustomEmoji --cache-key-tmpl="atto.emoji:{}");
27
28    /// Get all emojis by community.
29    ///
30    /// # Arguments
31    /// * `community` - the ID of the community to fetch emojis for
32    pub async fn get_emojis_by_community(&self, community: usize) -> Result<Vec<CustomEmoji>> {
33        let conn = match self.0.connect().await {
34            Ok(c) => c,
35            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
36        };
37
38        let res = query_rows!(
39            &conn,
40            "SELECT * FROM emojis WHERE community = $1 ORDER BY name ASC",
41            &[&(community as i64)],
42            |x| { Self::get_emoji_from_row(x) }
43        );
44
45        if res.is_err() {
46            return Err(Error::GeneralNotFound("emoji".to_string()));
47        }
48
49        Ok(res.unwrap())
50    }
51
52    /// Get all emojis by their community for the communities the given user is in.
53    pub async fn get_user_emojis(
54        &self,
55        id: usize,
56    ) -> Result<HashMap<usize, (String, Vec<CustomEmoji>)>> {
57        let memberships = self.get_memberships_by_owner(id).await?;
58        let mut out = HashMap::new();
59
60        for membership in memberships {
61            let community = self.get_community_by_id(membership.community).await?;
62
63            out.insert(
64                community.id,
65                (
66                    community.title.clone(),
67                    self.get_emojis_by_community(community.id).await?,
68                ),
69            );
70        }
71
72        Ok(out)
73    }
74
75    /// Get an emoji by community and name.
76    ///
77    /// # Arguments
78    /// * `community` - the ID of the community to fetch emoji from
79    /// * `name` - the name of the emoji
80    ///
81    /// # Returns
82    /// `(custom emoji, emoji string)`
83    ///
84    /// Custom emoji will be none if emoji string is some, and vice versa. Emoji string
85    /// will only be some if the community is 0 (no community ID in parsed string, or `0.emoji_name`)
86    ///
87    /// Regular unicode emojis should have a community ID of 0, since they don't belong
88    /// to any community and can be used by anyone.
89    pub async fn get_emoji_by_community_name(
90        &self,
91        community: usize,
92        name: &str,
93    ) -> Result<(Option<CustomEmoji>, Option<String>)> {
94        if community == 0 {
95            return Ok((None, Some("🐇".to_string())));
96        }
97
98        // ...
99        let conn = match self.0.connect().await {
100            Ok(c) => c,
101            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
102        };
103
104        let res = query_row!(
105            &conn,
106            "SELECT * FROM emojis WHERE community = $1 AND name = $2 ORDER BY name ASC",
107            params![&(community as i64), &name],
108            |x| { Ok((Some(Self::get_emoji_from_row(x)), None)) }
109        );
110
111        if res.is_err() {
112            return Err(Error::GeneralNotFound("emoji".to_string()));
113        }
114
115        Ok(res.unwrap())
116    }
117
118    /// Create a new emoji in the database.
119    ///
120    /// # Arguments
121    /// * `data` - a mock [`CustomEmoji`] object to insert
122    pub async fn create_emoji(&self, data: CustomEmoji) -> Result<()> {
123        let user = self.get_user_by_id(data.owner).await?;
124
125        // check if we can create animated emojis
126        if !user.permissions.check(FinePermission::SUPPORTER) && data.is_animated {
127            return Err(Error::RequiresSupporter);
128        }
129
130        // check user permission in community
131        if data.community != 0 {
132            let membership = self
133                .get_membership_by_owner_community(user.id, data.community)
134                .await?;
135
136            if !membership.role.check(CommunityPermission::MANAGE_EMOJIS)
137                && !user.permissions.check(FinePermission::MANAGE_EMOJIS)
138            {
139                return Err(Error::NotAllowed);
140            }
141        }
142
143        // ...
144        let conn = match self.0.connect().await {
145            Ok(c) => c,
146            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
147        };
148
149        let res = execute!(
150            &conn,
151            "INSERT INTO emojis VALUES ($1, $2, $3, $4, $5, $6, $7)",
152            params![
153                &(data.id as i64),
154                &(data.created as i64),
155                &(data.owner as i64),
156                &(data.community as i64),
157                &(data.upload_id as i64),
158                &data.name,
159                &{ if data.is_animated { 1 } else { 0 } },
160            ]
161        );
162
163        if let Err(e) = res {
164            return Err(Error::DatabaseError(e.to_string()));
165        }
166
167        Ok(())
168    }
169
170    pub async fn delete_emoji(&self, id: usize, user: &User) -> Result<()> {
171        let emoji = self.get_emoji_by_id(id).await?;
172
173        // check user permission in community
174        if user.id != emoji.owner {
175            let membership = self
176                .get_membership_by_owner_community(user.id, emoji.community)
177                .await?;
178
179            if !membership.role.check(CommunityPermission::MANAGE_EMOJIS) {
180                return Err(Error::NotAllowed);
181            }
182        }
183
184        // ...
185        let conn = match self.0.connect().await {
186            Ok(c) => c,
187            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
188        };
189
190        let res = execute!(&conn, "DELETE FROM emojis WHERE id = $1", &[&(id as i64)]);
191
192        if let Err(e) = res {
193            return Err(Error::DatabaseError(e.to_string()));
194        }
195
196        // delete upload
197        self.delete_upload(emoji.upload_id).await?;
198
199        // ...
200        self.0.1.remove(format!("atto.emoji:{}", id)).await;
201        Ok(())
202    }
203
204    auto_method!(update_emoji_name(&str)@get_emoji_by_id:FinePermission::MANAGE_EMOJIS; -> "UPDATE emojis SET name = $1 WHERE id = $2" --cache-key-tmpl="atto.emoji:{}");
205}