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}