backend/objects/
channel.rs

1use diesel::{
2    ExpressionMethods, Insertable, QueryDsl, Queryable, Selectable, SelectableHelper, delete,
3    insert_into, update,
4};
5use diesel_async::{RunQueryDsl, pooled_connection::AsyncDieselConnectionManager};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9use crate::{
10    Conn, Data,
11    error::Error,
12    schema::{channel_permissions, channels, messages},
13    utils::{CHANNEL_REGEX, order_by_is_above},
14};
15
16use super::{HasIsAbove, HasUuid, Message, load_or_empty, message::MessageBuilder};
17
18#[derive(Queryable, Selectable, Insertable, Clone, Debug)]
19#[diesel(table_name = channels)]
20#[diesel(check_for_backend(diesel::pg::Pg))]
21struct ChannelBuilder {
22    uuid: Uuid,
23    guild_uuid: Uuid,
24    name: String,
25    description: Option<String>,
26    is_above: Option<Uuid>,
27}
28
29impl ChannelBuilder {
30    async fn build(self, conn: &mut Conn) -> Result<Channel, Error> {
31        use self::channel_permissions::dsl::*;
32        let channel_permission: Vec<ChannelPermission> = load_or_empty(
33            channel_permissions
34                .filter(channel_uuid.eq(self.uuid))
35                .select(ChannelPermission::as_select())
36                .load(conn)
37                .await,
38        )?;
39
40        Ok(Channel {
41            uuid: self.uuid,
42            guild_uuid: self.guild_uuid,
43            name: self.name,
44            description: self.description,
45            is_above: self.is_above,
46            permissions: channel_permission,
47        })
48    }
49}
50
51#[derive(Serialize, Deserialize, Clone, Debug)]
52pub struct Channel {
53    pub uuid: Uuid,
54    pub guild_uuid: Uuid,
55    name: String,
56    description: Option<String>,
57    pub is_above: Option<Uuid>,
58    pub permissions: Vec<ChannelPermission>,
59}
60
61#[derive(Serialize, Deserialize, Clone, Queryable, Selectable, Debug)]
62#[diesel(table_name = channel_permissions)]
63#[diesel(check_for_backend(diesel::pg::Pg))]
64pub struct ChannelPermission {
65    pub role_uuid: Uuid,
66    pub permissions: i64,
67}
68
69impl HasUuid for Channel {
70    fn uuid(&self) -> &Uuid {
71        self.uuid.as_ref()
72    }
73}
74
75impl HasIsAbove for Channel {
76    fn is_above(&self) -> Option<&Uuid> {
77        self.is_above.as_ref()
78    }
79}
80
81impl Channel {
82    pub async fn fetch_all(
83        pool: &deadpool::managed::Pool<
84            AsyncDieselConnectionManager<diesel_async::AsyncPgConnection>,
85            Conn,
86        >,
87        guild_uuid: Uuid,
88    ) -> Result<Vec<Self>, Error> {
89        let mut conn = pool.get().await?;
90
91        use channels::dsl;
92        let channel_builders: Vec<ChannelBuilder> = load_or_empty(
93            dsl::channels
94                .filter(dsl::guild_uuid.eq(guild_uuid))
95                .select(ChannelBuilder::as_select())
96                .load(&mut conn)
97                .await,
98        )?;
99
100        let channel_futures = channel_builders.iter().map(async move |c| {
101            let mut conn = pool.get().await?;
102            c.clone().build(&mut conn).await
103        });
104
105        futures::future::try_join_all(channel_futures).await
106    }
107
108    pub async fn fetch_one(data: &Data, channel_uuid: Uuid) -> Result<Self, Error> {
109        if let Ok(cache_hit) = data.get_cache_key(channel_uuid.to_string()).await {
110            return Ok(serde_json::from_str(&cache_hit)?);
111        }
112
113        let mut conn = data.pool.get().await?;
114
115        use channels::dsl;
116        let channel_builder: ChannelBuilder = dsl::channels
117            .filter(dsl::uuid.eq(channel_uuid))
118            .select(ChannelBuilder::as_select())
119            .get_result(&mut conn)
120            .await?;
121
122        let channel = channel_builder.build(&mut conn).await?;
123
124        data.set_cache_key(channel_uuid.to_string(), channel.clone(), 60)
125            .await?;
126
127        Ok(channel)
128    }
129
130    pub async fn new(
131        data: actix_web::web::Data<Data>,
132        guild_uuid: Uuid,
133        name: String,
134        description: Option<String>,
135    ) -> Result<Self, Error> {
136        if !CHANNEL_REGEX.is_match(&name) {
137            return Err(Error::BadRequest("Channel name is invalid".to_string()));
138        }
139
140        let mut conn = data.pool.get().await?;
141
142        let channel_uuid = Uuid::now_v7();
143
144        let channels = Self::fetch_all(&data.pool, guild_uuid).await?;
145
146        let channels_ordered = order_by_is_above(channels).await?;
147
148        let last_channel = channels_ordered.last();
149
150        let new_channel = ChannelBuilder {
151            uuid: channel_uuid,
152            guild_uuid,
153            name: name.clone(),
154            description: description.clone(),
155            is_above: None,
156        };
157
158        insert_into(channels::table)
159            .values(new_channel.clone())
160            .execute(&mut conn)
161            .await?;
162
163        if let Some(old_last_channel) = last_channel {
164            use channels::dsl;
165            update(channels::table)
166                .filter(dsl::uuid.eq(old_last_channel.uuid))
167                .set(dsl::is_above.eq(new_channel.uuid))
168                .execute(&mut conn)
169                .await?;
170        }
171
172        // returns different object because there's no reason to build the channelbuilder (wastes 1 database request)
173        let channel = Self {
174            uuid: channel_uuid,
175            guild_uuid,
176            name,
177            description,
178            is_above: None,
179            permissions: vec![],
180        };
181
182        data.set_cache_key(channel_uuid.to_string(), channel.clone(), 1800)
183            .await?;
184
185        if data
186            .get_cache_key(format!("{}_channels", guild_uuid))
187            .await
188            .is_ok()
189        {
190            data.del_cache_key(format!("{}_channels", guild_uuid))
191                .await?;
192        }
193
194        Ok(channel)
195    }
196
197    pub async fn delete(self, data: &Data) -> Result<(), Error> {
198        let mut conn = data.pool.get().await?;
199
200        use channels::dsl;
201        match update(channels::table)
202            .filter(dsl::is_above.eq(self.uuid))
203            .set(dsl::is_above.eq(None::<Uuid>))
204            .execute(&mut conn)
205            .await
206        {
207            Ok(r) => Ok(r),
208            Err(diesel::result::Error::NotFound) => Ok(0),
209            Err(e) => Err(e),
210        }?;
211
212        delete(channels::table)
213            .filter(dsl::uuid.eq(self.uuid))
214            .execute(&mut conn)
215            .await?;
216
217        match update(channels::table)
218            .filter(dsl::is_above.eq(self.uuid))
219            .set(dsl::is_above.eq(self.is_above))
220            .execute(&mut conn)
221            .await
222        {
223            Ok(r) => Ok(r),
224            Err(diesel::result::Error::NotFound) => Ok(0),
225            Err(e) => Err(e),
226        }?;
227
228        if data.get_cache_key(self.uuid.to_string()).await.is_ok() {
229            data.del_cache_key(self.uuid.to_string()).await?;
230        }
231
232        if data
233            .get_cache_key(format!("{}_channels", self.guild_uuid))
234            .await
235            .is_ok()
236        {
237            data.del_cache_key(format!("{}_channels", self.guild_uuid))
238                .await?;
239        }
240
241        Ok(())
242    }
243
244    pub async fn fetch_messages(
245        &self,
246        data: &Data,
247        amount: i64,
248        offset: i64,
249    ) -> Result<Vec<Message>, Error> {
250        let mut conn = data.pool.get().await?;
251
252        use messages::dsl;
253        let messages: Vec<MessageBuilder> = load_or_empty(
254            dsl::messages
255                .filter(dsl::channel_uuid.eq(self.uuid))
256                .select(MessageBuilder::as_select())
257                .order(dsl::uuid.desc())
258                .limit(amount)
259                .offset(offset)
260                .load(&mut conn)
261                .await,
262        )?;
263
264        let message_futures = messages.iter().map(async move |b| b.build(data).await);
265
266        futures::future::try_join_all(message_futures).await
267    }
268
269    pub async fn new_message(
270        &self,
271        data: &Data,
272        user_uuid: Uuid,
273        message: String,
274    ) -> Result<Message, Error> {
275        let message_uuid = Uuid::now_v7();
276
277        let message = MessageBuilder {
278            uuid: message_uuid,
279            channel_uuid: self.uuid,
280            user_uuid,
281            message,
282        };
283
284        let mut conn = data.pool.get().await?;
285
286        insert_into(messages::table)
287            .values(message.clone())
288            .execute(&mut conn)
289            .await?;
290
291        message.build(data).await
292    }
293
294    pub async fn set_name(&mut self, data: &Data, new_name: String) -> Result<(), Error> {
295        if !CHANNEL_REGEX.is_match(&new_name) {
296            return Err(Error::BadRequest("Channel name is invalid".to_string()));
297        }
298
299        let mut conn = data.pool.get().await?;
300
301        use channels::dsl;
302        update(channels::table)
303            .filter(dsl::uuid.eq(self.uuid))
304            .set(dsl::name.eq(&new_name))
305            .execute(&mut conn)
306            .await?;
307
308        self.name = new_name;
309
310        Ok(())
311    }
312
313    pub async fn set_description(
314        &mut self,
315        data: &Data,
316        new_description: String,
317    ) -> Result<(), Error> {
318        let mut conn = data.pool.get().await?;
319
320        use channels::dsl;
321        update(channels::table)
322            .filter(dsl::uuid.eq(self.uuid))
323            .set(dsl::description.eq(&new_description))
324            .execute(&mut conn)
325            .await?;
326
327        self.description = Some(new_description);
328
329        Ok(())
330    }
331
332    pub async fn move_channel(&mut self, data: &Data, new_is_above: Uuid) -> Result<(), Error> {
333        let mut conn = data.pool.get().await?;
334
335        use channels::dsl;
336        let old_above_uuid: Option<Uuid> = match dsl::channels
337            .filter(dsl::is_above.eq(self.uuid))
338            .select(dsl::uuid)
339            .get_result(&mut conn)
340            .await
341        {
342            Ok(r) => Ok(Some(r)),
343            Err(diesel::result::Error::NotFound) => Ok(None),
344            Err(e) => Err(e),
345        }?;
346
347        if let Some(uuid) = old_above_uuid {
348            update(channels::table)
349                .filter(dsl::uuid.eq(uuid))
350                .set(dsl::is_above.eq(None::<Uuid>))
351                .execute(&mut conn)
352                .await?;
353        }
354
355        match update(channels::table)
356            .filter(dsl::is_above.eq(new_is_above))
357            .set(dsl::is_above.eq(self.uuid))
358            .execute(&mut conn)
359            .await
360        {
361            Ok(r) => Ok(r),
362            Err(diesel::result::Error::NotFound) => Ok(0),
363            Err(e) => Err(e),
364        }?;
365
366        update(channels::table)
367            .filter(dsl::uuid.eq(self.uuid))
368            .set(dsl::is_above.eq(new_is_above))
369            .execute(&mut conn)
370            .await?;
371
372        if let Some(uuid) = old_above_uuid {
373            update(channels::table)
374                .filter(dsl::uuid.eq(uuid))
375                .set(dsl::is_above.eq(self.is_above))
376                .execute(&mut conn)
377                .await?;
378        }
379
380        self.is_above = Some(new_is_above);
381
382        Ok(())
383    }
384}