backend/objects/
member.rs

1use diesel::{
2    Associations, BoolExpressionMethods, ExpressionMethods, Identifiable, Insertable, JoinOnDsl,
3    QueryDsl, Queryable, Selectable, SelectableHelper, define_sql_function, delete, insert_into,
4    sql_types::{Nullable, VarChar},
5};
6use diesel_async::RunQueryDsl;
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use crate::{
11    Conn,
12    error::Error,
13    objects::PaginationRequest,
14    schema::{friends, guild_bans, guild_members, users},
15};
16
17use super::{
18    Friend, Guild, GuildBan, Me, Pagination, Permissions, Role, User, load_or_empty,
19    user::UserBuilder,
20};
21
22define_sql_function! { fn coalesce(x: Nullable<VarChar>, y: Nullable<VarChar>, z: VarChar) -> Text; }
23
24#[derive(Serialize, Queryable, Identifiable, Selectable, Insertable, Associations)]
25#[diesel(table_name = guild_members)]
26#[diesel(belongs_to(UserBuilder, foreign_key = user_uuid))]
27#[diesel(belongs_to(Guild, foreign_key = guild_uuid))]
28#[diesel(primary_key(uuid))]
29#[diesel(check_for_backend(diesel::pg::Pg))]
30pub struct MemberBuilder {
31    pub uuid: Uuid,
32    pub nickname: Option<String>,
33    pub user_uuid: Uuid,
34    pub guild_uuid: Uuid,
35    pub is_owner: bool,
36}
37
38impl MemberBuilder {
39    pub async fn build(
40        &self,
41        conn: &mut Conn,
42        cache_pool: &redis::Client,
43        me: Option<&Me>,
44    ) -> Result<Member, Error> {
45        let user;
46
47        if let Some(me) = me {
48            user = User::fetch_one_with_friendship(conn, cache_pool, me, self.user_uuid).await?;
49        } else {
50            user = User::fetch_one(conn, cache_pool, self.user_uuid).await?;
51        }
52
53        let roles = Role::fetch_from_member(conn, cache_pool, self).await?;
54
55        Ok(Member {
56            uuid: self.uuid,
57            nickname: self.nickname.clone(),
58            user_uuid: self.user_uuid,
59            guild_uuid: self.guild_uuid,
60            is_owner: self.is_owner,
61            user,
62            roles,
63        })
64    }
65
66    async fn build_with_parts(
67        &self,
68        conn: &mut Conn,
69        cache_pool: &redis::Client,
70        user_builder: UserBuilder,
71        friend: Option<Friend>,
72    ) -> Result<Member, Error> {
73        let mut user = user_builder.build();
74
75        if let Some(friend) = friend {
76            user.friends_since = Some(friend.accepted_at);
77        }
78
79        let roles = Role::fetch_from_member(conn, cache_pool, self).await?;
80
81        Ok(Member {
82            uuid: self.uuid,
83            nickname: self.nickname.clone(),
84            user_uuid: self.user_uuid,
85            guild_uuid: self.guild_uuid,
86            is_owner: self.is_owner,
87            user,
88            roles,
89        })
90    }
91
92    pub async fn check_permission(
93        &self,
94        conn: &mut Conn,
95        cache_pool: &redis::Client,
96        permission: Permissions,
97    ) -> Result<(), Error> {
98        if !self.is_owner {
99            let roles = Role::fetch_from_member(conn, cache_pool, self).await?;
100            let allowed = roles.iter().any(|r| r.permissions & permission as i64 != 0);
101            if !allowed {
102                return Err(Error::Forbidden("Not allowed".to_string()));
103            }
104        }
105
106        Ok(())
107    }
108}
109
110#[derive(Serialize, Deserialize, Clone)]
111pub struct Member {
112    pub uuid: Uuid,
113    pub nickname: Option<String>,
114    #[serde(skip)]
115    pub user_uuid: Uuid,
116    pub guild_uuid: Uuid,
117    pub is_owner: bool,
118    user: User,
119    roles: Vec<Role>,
120}
121
122impl Member {
123    pub async fn count(conn: &mut Conn, guild_uuid: Uuid) -> Result<i64, Error> {
124        use guild_members::dsl;
125        let count: i64 = dsl::guild_members
126            .filter(dsl::guild_uuid.eq(guild_uuid))
127            .count()
128            .get_result(conn)
129            .await?;
130
131        Ok(count)
132    }
133
134    pub async fn check_membership(
135        conn: &mut Conn,
136        user_uuid: Uuid,
137        guild_uuid: Uuid,
138    ) -> Result<MemberBuilder, Error> {
139        use guild_members::dsl;
140        let member_builder = dsl::guild_members
141            .filter(dsl::user_uuid.eq(user_uuid))
142            .filter(dsl::guild_uuid.eq(guild_uuid))
143            .select(MemberBuilder::as_select())
144            .get_result(conn)
145            .await?;
146
147        Ok(member_builder)
148    }
149
150    pub async fn fetch_one(
151        conn: &mut Conn,
152        cache_pool: &redis::Client,
153        me: Option<&Me>,
154        user_uuid: Uuid,
155        guild_uuid: Uuid,
156    ) -> Result<Self, Error> {
157        let member: MemberBuilder;
158        let user: UserBuilder;
159        let friend: Option<Friend>;
160        use friends::dsl as fdsl;
161        use guild_members::dsl;
162        if let Some(me) = me {
163            (member, user, friend) = dsl::guild_members
164                .filter(dsl::guild_uuid.eq(guild_uuid))
165                .filter(dsl::user_uuid.eq(user_uuid))
166                .inner_join(users::table)
167                .left_join(
168                    fdsl::friends.on(fdsl::uuid1
169                        .eq(me.uuid)
170                        .and(fdsl::uuid2.eq(users::uuid))
171                        .or(fdsl::uuid2.eq(me.uuid).and(fdsl::uuid1.eq(users::uuid)))),
172                )
173                .select((
174                    MemberBuilder::as_select(),
175                    UserBuilder::as_select(),
176                    Option::<Friend>::as_select(),
177                ))
178                .get_result(conn)
179                .await?;
180        } else {
181            (member, user) = dsl::guild_members
182                .filter(dsl::guild_uuid.eq(guild_uuid))
183                .filter(dsl::user_uuid.eq(user_uuid))
184                .inner_join(users::table)
185                .select((MemberBuilder::as_select(), UserBuilder::as_select()))
186                .get_result(conn)
187                .await?;
188
189            friend = None;
190        }
191
192        member
193            .build_with_parts(conn, cache_pool, user, friend)
194            .await
195    }
196
197    pub async fn fetch_one_with_uuid(
198        conn: &mut Conn,
199        cache_pool: &redis::Client,
200        me: Option<&Me>,
201        uuid: Uuid,
202    ) -> Result<Self, Error> {
203        let member: MemberBuilder;
204        let user: UserBuilder;
205        let friend: Option<Friend>;
206        use friends::dsl as fdsl;
207        use guild_members::dsl;
208        if let Some(me) = me {
209            (member, user, friend) = dsl::guild_members
210                .filter(dsl::uuid.eq(uuid))
211                .inner_join(users::table)
212                .left_join(
213                    fdsl::friends.on(fdsl::uuid1
214                        .eq(me.uuid)
215                        .and(fdsl::uuid2.eq(users::uuid))
216                        .or(fdsl::uuid2.eq(me.uuid).and(fdsl::uuid1.eq(users::uuid)))),
217                )
218                .select((
219                    MemberBuilder::as_select(),
220                    UserBuilder::as_select(),
221                    Option::<Friend>::as_select(),
222                ))
223                .get_result(conn)
224                .await?;
225        } else {
226            (member, user) = dsl::guild_members
227                .filter(dsl::uuid.eq(uuid))
228                .inner_join(users::table)
229                .select((MemberBuilder::as_select(), UserBuilder::as_select()))
230                .get_result(conn)
231                .await?;
232
233            friend = None;
234        }
235
236        member
237            .build_with_parts(conn, cache_pool, user, friend)
238            .await
239    }
240
241    pub async fn fetch_page(
242        conn: &mut Conn,
243        cache_pool: &redis::Client,
244        me: &Me,
245        guild_uuid: Uuid,
246        pagination: PaginationRequest,
247    ) -> Result<Pagination<Self>, Error> {
248        let per_page = pagination.per_page.unwrap_or(50);
249        let page_multiplier: i64 = ((pagination.page - 1) * per_page).into();
250
251        if !(10..=100).contains(&per_page) {
252            return Err(Error::BadRequest(
253                "Invalid amount per page requested".to_string(),
254            ));
255        }
256
257        use friends::dsl as fdsl;
258        use guild_members::dsl;
259        let member_builders: Vec<(MemberBuilder, UserBuilder, Option<Friend>)> = load_or_empty(
260            dsl::guild_members
261                .filter(dsl::guild_uuid.eq(guild_uuid))
262                .inner_join(users::table)
263                .left_join(
264                    fdsl::friends.on(fdsl::uuid1
265                        .eq(me.uuid)
266                        .and(fdsl::uuid2.eq(users::uuid))
267                        .or(fdsl::uuid2.eq(me.uuid).and(fdsl::uuid1.eq(users::uuid)))),
268                )
269                .limit(per_page.into())
270                .offset(page_multiplier)
271                .order_by(coalesce(
272                    dsl::nickname,
273                    users::display_name,
274                    users::username,
275                ))
276                .select((
277                    MemberBuilder::as_select(),
278                    UserBuilder::as_select(),
279                    Option::<Friend>::as_select(),
280                ))
281                .load(conn)
282                .await,
283        )?;
284
285        let pages = Member::count(conn, guild_uuid).await? as f32 / per_page as f32;
286
287        let mut members = Pagination::<Member> {
288            objects: Vec::with_capacity(member_builders.len()),
289            amount: member_builders.len() as i32,
290            pages: pages.ceil() as i32,
291            page: pagination.page,
292        };
293
294        for (member, user, friend) in member_builders {
295            members.objects.push(
296                member
297                    .build_with_parts(conn, cache_pool, user, friend)
298                    .await?,
299            );
300        }
301
302        Ok(members)
303    }
304
305    pub async fn new(
306        conn: &mut Conn,
307        cache_pool: &redis::Client,
308        user_uuid: Uuid,
309        guild_uuid: Uuid,
310    ) -> Result<Self, Error> {
311        let banned = GuildBan::fetch_one(conn, guild_uuid, user_uuid).await;
312
313        match banned {
314            Ok(_) => Err(Error::Forbidden("User banned".to_string())),
315            Err(Error::SqlError(diesel::result::Error::NotFound)) => Ok(()),
316            Err(e) => Err(e),
317        }?;
318
319        let member_uuid = Uuid::now_v7();
320
321        let member = MemberBuilder {
322            uuid: member_uuid,
323            guild_uuid,
324            user_uuid,
325            nickname: None,
326            is_owner: false,
327        };
328
329        insert_into(guild_members::table)
330            .values(&member)
331            .execute(conn)
332            .await?;
333
334        member.build(conn, cache_pool, None).await
335    }
336
337    pub async fn delete(self, conn: &mut Conn) -> Result<(), Error> {
338        if self.is_owner {
339            return Err(Error::Forbidden("Can not kick owner".to_string()));
340        }
341        delete(guild_members::table)
342            .filter(guild_members::uuid.eq(self.uuid))
343            .execute(conn)
344            .await?;
345
346        Ok(())
347    }
348
349    pub async fn ban(self, conn: &mut Conn, reason: &String) -> Result<(), Error> {
350        if self.is_owner {
351            return Err(Error::Forbidden("Can not ban owner".to_string()));
352        }
353
354        use guild_bans::dsl;
355        insert_into(guild_bans::table)
356            .values((
357                dsl::guild_uuid.eq(self.guild_uuid),
358                dsl::user_uuid.eq(self.user_uuid),
359                dsl::reason.eq(reason),
360            ))
361            .execute(conn)
362            .await?;
363
364        self.delete(conn).await?;
365
366        Ok(())
367    }
368
369    pub fn to_builder(&self) -> MemberBuilder {
370        MemberBuilder {
371            uuid: self.uuid,
372            nickname: self.nickname.clone(),
373            user_uuid: self.user_uuid,
374            guild_uuid: self.guild_uuid,
375            is_owner: self.is_owner,
376        }
377    }
378}