tetratto_core/database/
channels.rs

1use oiseau::cache::Cache;
2use crate::model::moderation::AuditLogEntry;
3use crate::model::{
4    Error, Result, auth::User, permissions::FinePermission,
5    communities_permissions::CommunityPermission, channels::Channel,
6};
7use crate::{auto_method, DataManager};
8use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};
9
10impl DataManager {
11    /// Get a [`Channel`] from an SQL row.
12    pub(crate) fn get_channel_from_row(x: &PostgresRow) -> Channel {
13        Channel {
14            id: get!(x->0(i64)) as usize,
15            community: get!(x->1(i64)) as usize,
16            owner: get!(x->2(i64)) as usize,
17            created: get!(x->3(i64)) as usize,
18            minimum_role_read: get!(x->4(i32)) as u32,
19            minimum_role_write: get!(x->5(i32)) as u32,
20            position: get!(x->6(i32)) as usize,
21            members: serde_json::from_str(&get!(x->7(String))).unwrap(),
22            title: get!(x->8(String)),
23            last_message: get!(x->9(i64)) as usize,
24        }
25    }
26
27    auto_method!(get_channel_by_id(usize as i64)@get_channel_from_row -> "SELECT * FROM channels WHERE id = $1" --name="channel" --returns=Channel --cache-key-tmpl="atto.channel:{}");
28
29    /// Get all member profiles from a channel members list.
30    pub async fn fill_members(
31        &self,
32        members: &Vec<usize>,
33        ignore_users: Vec<usize>,
34    ) -> Result<Vec<User>> {
35        let mut out = Vec::new();
36
37        for member in members {
38            if ignore_users.contains(member) {
39                continue;
40            }
41
42            out.push(self.get_user_by_id(member.to_owned()).await?);
43        }
44
45        Ok(out)
46    }
47
48    /// Get all channels by community.
49    ///
50    /// # Arguments
51    /// * `community` - the ID of the community to fetch channels for
52    pub async fn get_channels_by_community(&self, community: usize) -> Result<Vec<Channel>> {
53        let conn = match self.0.connect().await {
54            Ok(c) => c,
55            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
56        };
57
58        let res = query_rows!(
59            &conn,
60            "SELECT * FROM channels WHERE community = $1 ORDER BY position ASC",
61            &[&(community as i64)],
62            |x| { Self::get_channel_from_row(x) }
63        );
64
65        if res.is_err() {
66            return Err(Error::GeneralNotFound("channel".to_string()));
67        }
68
69        Ok(res.unwrap())
70    }
71
72    /// Get all channels by user.
73    ///
74    /// # Arguments
75    /// * `user` - the ID of the user to fetch channels for
76    pub async fn get_channels_by_user(&self, user: usize) -> Result<Vec<Channel>> {
77        let conn = match self.0.connect().await {
78            Ok(c) => c,
79            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
80        };
81
82        let res = query_rows!(
83            &conn,
84            "SELECT * FROM channels WHERE (owner = $1 OR members LIKE $2) AND community = 0 ORDER BY last_message DESC",
85            params![&(user as i64), &format!("%{user}%")],
86            |x| { Self::get_channel_from_row(x) }
87        );
88
89        if res.is_err() {
90            return Err(Error::GeneralNotFound("channel".to_string()));
91        }
92
93        Ok(res.unwrap())
94    }
95
96    /// Get a channel given its `owner` and a member.
97    ///
98    /// # Arguments
99    /// * `owner` - the ID of the owner
100    /// * `member` - the ID of the member
101    pub async fn get_channel_by_owner_member(
102        &self,
103        owner: usize,
104        member: usize,
105    ) -> Result<Channel> {
106        let conn = match self.0.connect().await {
107            Ok(c) => c,
108            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
109        };
110
111        let res = query_row!(
112            &conn,
113            "SELECT * FROM channels WHERE owner = $1 AND members = $2 AND community = 0 ORDER BY created DESC",
114            params![&(owner as i64), &format!("[{member}]")],
115            |x| { Ok(Self::get_channel_from_row(x)) }
116        );
117
118        if res.is_err() {
119            return Err(Error::GeneralNotFound("channel".to_string()));
120        }
121
122        Ok(res.unwrap())
123    }
124
125    /// Create a new channel in the database.
126    ///
127    /// # Arguments
128    /// * `data` - a mock [`Channel`] object to insert
129    pub async fn create_channel(&self, data: Channel) -> Result<()> {
130        let user = self.get_user_by_id(data.owner).await?;
131
132        // check user permission in community
133        if data.community != 0 {
134            let membership = self
135                .get_membership_by_owner_community(user.id, data.community)
136                .await?;
137
138            if !membership.role.check(CommunityPermission::MANAGE_CHANNELS)
139                && !user.permissions.check(FinePermission::MANAGE_CHANNELS)
140            {
141                return Err(Error::NotAllowed);
142            }
143        }
144        // check members
145        else {
146            for member in &data.members {
147                if self
148                    .get_userblock_by_initiator_receiver(member.to_owned(), data.owner)
149                    .await
150                    .is_ok()
151                {
152                    return Err(Error::NotAllowed);
153                }
154            }
155        }
156
157        // ...
158        let conn = match self.0.connect().await {
159            Ok(c) => c,
160            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
161        };
162
163        let res = execute!(
164            &conn,
165            "INSERT INTO channels VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
166            params![
167                &(data.id as i64),
168                &(data.community as i64),
169                &(data.owner as i64),
170                &(data.created as i64),
171                &(data.minimum_role_read as i32),
172                &(data.minimum_role_write as i32),
173                &(data.position as i32),
174                &serde_json::to_string(&data.members).unwrap(),
175                &data.title,
176                &(data.last_message as i64)
177            ]
178        );
179
180        if let Err(e) = res {
181            return Err(Error::DatabaseError(e.to_string()));
182        }
183
184        Ok(())
185    }
186
187    pub async fn delete_channel(&self, id: usize, user: &User) -> Result<()> {
188        let channel = self.get_channel_by_id(id).await?;
189
190        // check user permission in community
191        if user.id != channel.owner {
192            let membership = self
193                .get_membership_by_owner_community(user.id, channel.community)
194                .await?;
195
196            if !membership.role.check(CommunityPermission::MANAGE_CHANNELS) {
197                return Err(Error::NotAllowed);
198            }
199        }
200
201        // ...
202        let conn = match self.0.connect().await {
203            Ok(c) => c,
204            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
205        };
206
207        let res = execute!(&conn, "DELETE FROM channels WHERE id = $1", &[&(id as i64)]);
208
209        if let Err(e) = res {
210            return Err(Error::DatabaseError(e.to_string()));
211        }
212
213        // delete messages
214        let res = execute!(
215            &conn,
216            "DELETE FROM messages WHERE channel = $1",
217            &[&(id as i64)]
218        );
219
220        if let Err(e) = res {
221            return Err(Error::DatabaseError(e.to_string()));
222        }
223
224        // ...
225        self.0.1.remove(format!("atto.channel:{}", id)).await;
226        Ok(())
227    }
228
229    pub async fn add_channel_member(&self, id: usize, user: User, member: String) -> Result<()> {
230        let mut y = self.get_channel_by_id(id).await?;
231
232        if user.id != y.owner && member != user.username {
233            if !user.permissions.check(FinePermission::MANAGE_CHANNELS) {
234                return Err(Error::NotAllowed);
235            } else {
236                self.create_audit_log_entry(AuditLogEntry::new(
237                    user.id,
238                    format!("invoked `add_channel_member` with x value `{member}`"),
239                ))
240                .await?
241            }
242        }
243
244        // check permissions
245        let member = self.get_user_by_username(&member).await?;
246
247        if self
248            .get_userblock_by_initiator_receiver(member.id, user.id)
249            .await
250            .is_ok()
251        {
252            return Err(Error::NotAllowed);
253        }
254
255        // ...
256        y.members.push(member.id);
257
258        let conn = match self.0.connect().await {
259            Ok(c) => c,
260            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
261        };
262
263        let res = execute!(
264            &conn,
265            "UPDATE channels SET members = $1 WHERE id = $2",
266            params![&serde_json::to_string(&y.members).unwrap(), &(id as i64)]
267        );
268
269        if let Err(e) = res {
270            return Err(Error::DatabaseError(e.to_string()));
271        }
272
273        self.0.1.remove(format!("atto.channel:{}", id)).await;
274
275        Ok(())
276    }
277
278    pub async fn remove_channel_member(&self, id: usize, user: User, member: usize) -> Result<()> {
279        let mut y = self.get_channel_by_id(id).await?;
280
281        if user.id != y.owner && member != user.id {
282            if !user.permissions.check(FinePermission::MANAGE_CHANNELS) {
283                return Err(Error::NotAllowed);
284            } else {
285                self.create_audit_log_entry(AuditLogEntry::new(
286                    user.id,
287                    format!("invoked `remove_channel_member` with x value `{member}`"),
288                ))
289                .await?
290            }
291        }
292
293        y.members
294            .remove(match y.members.iter().position(|x| *x == member) {
295                Some(i) => i,
296                None => return Err(Error::GeneralNotFound("member".to_string())),
297            });
298
299        let conn = match self.0.connect().await {
300            Ok(c) => c,
301            Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
302        };
303
304        let res = execute!(
305            &conn,
306            "UPDATE channels SET members = $1 WHERE id = $2",
307            params![&serde_json::to_string(&y.members).unwrap(), &(id as i64)]
308        );
309
310        if let Err(e) = res {
311            return Err(Error::DatabaseError(e.to_string()));
312        }
313
314        self.0.1.remove(format!("atto.channel:{}", id)).await;
315
316        Ok(())
317    }
318
319    auto_method!(update_channel_title(&str)@get_channel_by_id:FinePermission::MANAGE_CHANNELS; -> "UPDATE channels SET title = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
320    auto_method!(update_channel_position(i32)@get_channel_by_id:FinePermission::MANAGE_CHANNELS; -> "UPDATE channels SET position = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
321    auto_method!(update_channel_minimum_role_read(i32)@get_channel_by_id:FinePermission::MANAGE_CHANNELS; -> "UPDATE channels SET minimum_role_read = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
322    auto_method!(update_channel_minimum_role_write(i32)@get_channel_by_id:FinePermission::MANAGE_CHANNELS; -> "UPDATE channels SET minimum_role_write = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
323    auto_method!(update_channel_members(Vec<usize>)@get_channel_by_id:FinePermission::MANAGE_CHANNELS; -> "UPDATE channels SET members = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.channel:{}");
324    auto_method!(update_channel_last_message(i64) -> "UPDATE channels SET last_message = $1 WHERE id = $2" --cache-key-tmpl="atto.channel:{}");
325}