1use axum::body::Bytes;
2use diesel::{
3 ExpressionMethods, Insertable, QueryDsl, Queryable, Selectable, SelectableHelper, insert_into,
4 update,
5};
6use diesel_async::RunQueryDsl;
7use serde::Serialize;
8use tokio::task;
9use url::Url;
10use uuid::Uuid;
11
12use crate::{
13 AppState, 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 conn: &mut Conn,
72 offset: i64,
73 amount: i64,
74 ) -> Result<Vec<Self>, Error> {
75 use guilds::dsl;
77 let guild_builders: Vec<GuildBuilder> = load_or_empty(
78 dsl::guilds
79 .select(GuildBuilder::as_select())
80 .order_by(dsl::uuid)
81 .offset(offset)
82 .limit(amount)
83 .load(conn)
84 .await,
85 )?;
86
87 let mut guilds = vec![];
88
89 for builder in guild_builders {
90 guilds.push(builder.build(conn).await?);
91 }
92
93 Ok(guilds)
94 }
95
96 pub async fn new(conn: &mut Conn, name: String, owner_uuid: Uuid) -> Result<Self, Error> {
97 let guild_uuid = Uuid::now_v7();
98
99 let guild_builder = GuildBuilder {
100 uuid: guild_uuid,
101 name: name.clone(),
102 description: None,
103 icon: None,
104 };
105
106 insert_into(guilds::table)
107 .values(guild_builder)
108 .execute(conn)
109 .await?;
110
111 let member_uuid = Uuid::now_v7();
112
113 let member = MemberBuilder {
114 uuid: member_uuid,
115 nickname: None,
116 user_uuid: owner_uuid,
117 guild_uuid,
118 is_owner: true,
119 };
120
121 insert_into(guild_members::table)
122 .values(member)
123 .execute(conn)
124 .await?;
125
126 Ok(Guild {
127 uuid: guild_uuid,
128 name,
129 description: None,
130 icon: None,
131 roles: vec![],
132 member_count: 1,
133 })
134 }
135
136 pub async fn get_invites(&self, conn: &mut Conn) -> Result<Vec<Invite>, Error> {
137 use invites::dsl;
138 let invites = load_or_empty(
139 dsl::invites
140 .filter(dsl::guild_uuid.eq(self.uuid))
141 .select(Invite::as_select())
142 .load(conn)
143 .await,
144 )?;
145
146 Ok(invites)
147 }
148
149 pub async fn create_invite(
150 &self,
151 conn: &mut Conn,
152 user_uuid: Uuid,
153 custom_id: Option<String>,
154 ) -> Result<Invite, Error> {
155 let invite_id;
156
157 if let Some(id) = custom_id {
158 invite_id = id;
159 if invite_id.len() > 32 {
160 return Err(Error::BadRequest("MAX LENGTH".to_string()));
161 }
162 } else {
163 let charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
164
165 invite_id = random_string::generate(8, charset);
166 }
167
168 let invite = Invite {
169 id: invite_id,
170 user_uuid,
171 guild_uuid: self.uuid,
172 };
173
174 insert_into(invites::table)
175 .values(invite.clone())
176 .execute(conn)
177 .await?;
178
179 Ok(invite)
180 }
181
182 pub async fn set_icon(
184 &mut self,
185 conn: &mut Conn,
186 app_state: &AppState,
187 icon: Bytes,
188 ) -> Result<(), Error> {
189 let icon_clone = icon.clone();
190 let image_type = task::spawn_blocking(move || image_check(icon_clone)).await??;
191
192 if let Some(icon) = &self.icon {
193 let relative_url = icon.path().trim_start_matches('/');
194
195 app_state.bunny_storage.delete(relative_url).await?;
196 }
197
198 let path = format!("icons/{}/{}.{}", self.uuid, Uuid::now_v7(), image_type);
199
200 app_state.bunny_storage.upload(path.clone(), icon).await?;
201
202 let icon_url = app_state.config.bunny.cdn_url.join(&path)?;
203
204 use guilds::dsl;
205 update(guilds::table)
206 .filter(dsl::uuid.eq(self.uuid))
207 .set(dsl::icon.eq(icon_url.as_str()))
208 .execute(conn)
209 .await?;
210
211 self.icon = Some(icon_url);
212
213 Ok(())
214 }
215}