1use actix_web::web::BytesMut;
2use diesel::{
3 ExpressionMethods, Insertable, QueryDsl, Queryable, Selectable, SelectableHelper, insert_into,
4 update,
5};
6use diesel_async::{RunQueryDsl, pooled_connection::AsyncDieselConnectionManager};
7use serde::Serialize;
8use tokio::task;
9use url::Url;
10use uuid::Uuid;
11
12use crate::{
13 Conn,
14 error::Error,
15 schema::{guild_members, guilds, invites},
16 utils::image_check,
17};
18
19use super::{Invite, Member, Role, load_or_empty, member::MemberBuilder};
20
21#[derive(Serialize, Queryable, Selectable, Insertable, Clone)]
22#[diesel(table_name = guilds)]
23#[diesel(check_for_backend(diesel::pg::Pg))]
24pub struct GuildBuilder {
25 uuid: Uuid,
26 name: String,
27 description: Option<String>,
28 icon: Option<String>,
29}
30
31impl GuildBuilder {
32 pub async fn build(self, conn: &mut Conn) -> Result<Guild, Error> {
33 let member_count = Member::count(conn, self.uuid).await?;
34
35 let roles = Role::fetch_all(conn, self.uuid).await?;
36
37 Ok(Guild {
38 uuid: self.uuid,
39 name: self.name,
40 description: self.description,
41 icon: self.icon.and_then(|i| i.parse().ok()),
42 roles,
43 member_count,
44 })
45 }
46}
47
48#[derive(Serialize)]
49pub struct Guild {
50 pub uuid: Uuid,
51 name: String,
52 description: Option<String>,
53 icon: Option<Url>,
54 pub roles: Vec<Role>,
55 member_count: i64,
56}
57
58impl Guild {
59 pub async fn fetch_one(conn: &mut Conn, guild_uuid: Uuid) -> Result<Self, Error> {
60 use guilds::dsl;
61 let guild_builder: GuildBuilder = dsl::guilds
62 .filter(dsl::uuid.eq(guild_uuid))
63 .select(GuildBuilder::as_select())
64 .get_result(conn)
65 .await?;
66
67 guild_builder.build(conn).await
68 }
69
70 pub async fn fetch_amount(
71 pool: &deadpool::managed::Pool<
72 AsyncDieselConnectionManager<diesel_async::AsyncPgConnection>,
73 Conn,
74 >,
75 offset: i64,
76 amount: i64,
77 ) -> Result<Vec<Self>, Error> {
78 let mut conn = pool.get().await?;
80
81 use guilds::dsl;
82 let guild_builders: Vec<GuildBuilder> = load_or_empty(
83 dsl::guilds
84 .select(GuildBuilder::as_select())
85 .order_by(dsl::uuid)
86 .offset(offset)
87 .limit(amount)
88 .load(&mut conn)
89 .await,
90 )?;
91
92 let guild_futures = guild_builders.iter().map(async move |g| {
94 let mut conn = pool.get().await?;
95 g.clone().build(&mut conn).await
96 });
97
98 futures::future::try_join_all(guild_futures).await
100 }
101
102 pub async fn new(conn: &mut Conn, name: String, owner_uuid: Uuid) -> Result<Self, Error> {
103 let guild_uuid = Uuid::now_v7();
104
105 let guild_builder = GuildBuilder {
106 uuid: guild_uuid,
107 name: name.clone(),
108 description: None,
109 icon: None,
110 };
111
112 insert_into(guilds::table)
113 .values(guild_builder)
114 .execute(conn)
115 .await?;
116
117 let member_uuid = Uuid::now_v7();
118
119 let member = MemberBuilder {
120 uuid: member_uuid,
121 nickname: None,
122 user_uuid: owner_uuid,
123 guild_uuid,
124 is_owner: true,
125 };
126
127 insert_into(guild_members::table)
128 .values(member)
129 .execute(conn)
130 .await?;
131
132 Ok(Guild {
133 uuid: guild_uuid,
134 name,
135 description: None,
136 icon: None,
137 roles: vec![],
138 member_count: 1,
139 })
140 }
141
142 pub async fn get_invites(&self, conn: &mut Conn) -> Result<Vec<Invite>, Error> {
143 use invites::dsl;
144 let invites = load_or_empty(
145 dsl::invites
146 .filter(dsl::guild_uuid.eq(self.uuid))
147 .select(Invite::as_select())
148 .load(conn)
149 .await,
150 )?;
151
152 Ok(invites)
153 }
154
155 pub async fn create_invite(
156 &self,
157 conn: &mut Conn,
158 user_uuid: Uuid,
159 custom_id: Option<String>,
160 ) -> Result<Invite, Error> {
161 let invite_id;
162
163 if let Some(id) = custom_id {
164 invite_id = id;
165 if invite_id.len() > 32 {
166 return Err(Error::BadRequest("MAX LENGTH".to_string()));
167 }
168 } else {
169 let charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
170
171 invite_id = random_string::generate(8, charset);
172 }
173
174 let invite = Invite {
175 id: invite_id,
176 user_uuid,
177 guild_uuid: self.uuid,
178 };
179
180 insert_into(invites::table)
181 .values(invite.clone())
182 .execute(conn)
183 .await?;
184
185 Ok(invite)
186 }
187
188 pub async fn set_icon(
190 &mut self,
191 bunny_cdn: &bunny_api_tokio::Client,
192 conn: &mut Conn,
193 cdn_url: Url,
194 icon: BytesMut,
195 ) -> Result<(), Error> {
196 let icon_clone = icon.clone();
197 let image_type = task::spawn_blocking(move || image_check(icon_clone)).await??;
198
199 if let Some(icon) = &self.icon {
200 let relative_url = icon.path().trim_start_matches('/');
201
202 bunny_cdn.storage.delete(relative_url).await?;
203 }
204
205 let path = format!("icons/{}/icon.{}", self.uuid, image_type);
206
207 bunny_cdn.storage.upload(path.clone(), icon.into()).await?;
208
209 let icon_url = cdn_url.join(&path)?;
210
211 use guilds::dsl;
212 update(guilds::table)
213 .filter(dsl::uuid.eq(self.uuid))
214 .set(dsl::icon.eq(icon_url.as_str()))
215 .execute(conn)
216 .await?;
217
218 self.icon = Some(icon_url);
219
220 Ok(())
221 }
222}