1use sqlx::{Postgres, QueryBuilder};
2
3use crate::{LibResult, Error};
4
5const TABLE_NAME: &str = "inv_groups";
6
7#[derive(Debug, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
8pub struct GroupDb {
9 #[serde(rename = "groupId")]
10 pub group_id: i32,
11 #[serde(rename = "categoryId")]
12 pub category_id: i32,
13 #[serde(rename = "groupName")]
14 pub group_name: String,
15 #[serde(rename = "iconId")]
16 pub icon_id: Option<i32>,
17 #[serde(rename = "useBasePrice")]
18 pub use_base_price: bool,
19 pub anchored: bool,
20 pub anchorable: bool,
21 #[serde(rename = "fittableNonSingleton")]
22 pub fittable_non_singleton: bool,
23 pub published: bool,
24}
25
26#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
27pub struct GroupFilter {
28 #[serde(rename = "groupID")]
29 pub group_id: Option<String>,
30 #[serde(rename = "categoryID")]
31 pub category_id: Option<String>,
32 #[serde(rename = "groupName")]
33 pub group_name: Option<String>,
34 pub page: Option<i64>,
35 pub limit: Option<i64>,
36}
37
38impl GroupDb {
39 pub async fn get_by_id(group_id: i32) -> LibResult<Self> {
40 let pool = crate::pool();
41 let group = sqlx::query_as::<_, Self>(&format!(
42 r#"
43 SELECT * FROM {}
44 WHERE group_id = $1
45 "#,
46 TABLE_NAME
47 ))
48 .bind(group_id)
49 .fetch_one(pool)
50 .await?;
51
52 Ok(group)
53 }
54
55 pub async fn count(filter: &GroupFilter) -> LibResult<i64> {
56 let pool = crate::pool();
57 let mut query = QueryBuilder::new(format!("SELECT COUNT(*) FROM {}", TABLE_NAME));
58 query = Self::build_query(query, filter);
59 let query = query.build_query_scalar();
60
61 match query.fetch_one(pool).await {
62 Ok(count) => Ok(count),
63 Err(err) => {
64 log::error!("Error counting items: {}", err);
65 return Err(Error::new(500, format!("Error counting items: {}", err)));
66 }
67 }
68 }
69
70 pub async fn get_multiple(filter: &GroupFilter) -> LibResult<Vec<Self>> {
71 let page = filter.page.unwrap_or(1);
72 let limit = filter.limit.unwrap_or(100);
73
74 let pool = crate::pool();
75 let mut query = QueryBuilder::new(format!("SELECT * FROM {}", TABLE_NAME));
76 query = Self::build_query(query, filter);
77 query.push(" ORDER BY group_id LIMIT ");
78 query.push_bind(limit);
79 query.push(" OFFSET ");
80 query.push_bind((page - 1) * limit);
81 let query = query.build_query_as();
82
83 match query.fetch_all(pool).await {
84 Ok(items) => Ok(items),
85 Err(err) => {
86 log::error!("Error fetching items: {}", err);
87 return Err(Error::new(500, format!("Error fetching items: {}", err)));
88 }
89 }
90 }
91
92 fn build_query<'a>(
93 mut query: QueryBuilder<'a, Postgres>,
94 filter: &'a GroupFilter,
95 ) -> QueryBuilder<'a, Postgres> {
96 let mut has_where = false;
97
98 if let Some(group_names) = &filter.group_name {
100 let group_names: Vec<&str> = group_names.split(',').collect();
101 if !has_where {
102 query.push(" WHERE");
103 has_where = true;
104 } else {
105 query.push(" AND");
106 }
107 query.push(" (");
108 for (i, group_name) in group_names.iter().enumerate() {
109 if i > 0 {
110 query.push(" OR");
111 }
112 query.push(" group_name ILIKE ");
113 query.push_bind(format!("%{}%", group_name));
114 }
115 query.push(" )");
116 }
117
118 if let Some(ids) = &filter.group_id {
120 let ids: Vec<&str> = ids.split(',').collect();
121 if !has_where {
122 query.push(" WHERE");
123 has_where = true;
124 } else {
125 query.push(" AND");
126 }
127 query.push(" (");
128 for (i, group_id) in ids.iter().enumerate() {
129 let group_id = match group_id.parse::<i32>() {
131 Ok(group_id) => group_id,
132 Err(_) => continue,
133 };
134 if i > 0 {
135 query.push(" OR");
136 }
137 query.push(" group_id = ");
138 query.push_bind(group_id);
139 }
140 query.push(" )");
141 }
142
143 if let Some(ids) = &filter.category_id {
145 let ids: Vec<&str> = ids.split(',').collect();
146 if !has_where {
147 query.push(" WHERE");
148 } else {
149 query.push(" AND");
150 }
151 query.push(" (");
152 for (i, category_id) in ids.iter().enumerate() {
153 let category_id = match category_id.parse::<i32>() {
155 Ok(category_id) => category_id,
156 Err(_) => continue,
157 };
158 if i > 0 {
159 query.push(" OR");
160 }
161 query.push(" category_id = ");
162 query.push_bind(category_id);
163 }
164 query.push(" )");
165 }
166
167 query
168 }
169
170 pub async fn insert(&self) -> LibResult<()> {
171 let pool = crate::pool();
172 sqlx::query(&format!(
173 "INSERT INTO {} (
174 group_id,
175 category_id,
176 group_name,
177 icon_id,
178 use_base_price,
179 anchored,
180 fittable_non_singleton,
181 published
182 ) VALUES (
183 $1, $2, $3, $4, $5, $6, $7, $8
184 )",
185 TABLE_NAME
186 ))
187 .bind(self.group_id)
188 .bind(self.category_id)
189 .bind(&self.group_name)
190 .bind(&self.icon_id)
191 .bind(self.use_base_price)
192 .bind(self.anchored)
193 .bind(self.fittable_non_singleton)
194 .bind(self.published)
195 .execute(pool)
196 .await?;
197 Ok(())
198 }
199
200 pub async fn insert_multiple(groups: &Vec<Self>) -> LibResult<()> {
201 let pool = crate::pool();
202 let step = 1000;
203 for i in (0..groups.len()).step_by(step) {
204 let mut query = sqlx::QueryBuilder::new(format!(
205 r#"
206 INSERT INTO {} (group_id, category_id, group_name, icon_id, use_base_price, anchored,
207 anchorable, fittable_non_singleton, published)
208 VALUES
209 "#,
210 TABLE_NAME
211 ));
212
213 for j in 0..step {
214 if i + j >= groups.len() {
215 break;
216 }
217 if j > 0 {
218 query.push(", ");
219 }
220 let group = &groups[i + j];
221 query
222 .push(" (")
223 .push_bind(group.group_id)
224 .push(", ")
225 .push_bind(group.category_id)
226 .push(", ")
227 .push_bind(&group.group_name)
228 .push(", ")
229 .push_bind(group.icon_id)
230 .push(", ")
231 .push_bind(group.use_base_price)
232 .push(", ")
233 .push_bind(group.anchored)
234 .push(", ")
235 .push_bind(group.anchorable)
236 .push(", ")
237 .push_bind(group.fittable_non_singleton)
238 .push(", ")
239 .push_bind(group.published)
240 .push(") ");
241 }
242 query
243 .push("ON CONFLICT (group_id) DO UPDATE SET ")
244 .push("category_id = EXCLUDED.category_id, ")
245 .push("group_name = EXCLUDED.group_name, ")
246 .push("icon_id = EXCLUDED.icon_id, ")
247 .push("use_base_price = EXCLUDED.use_base_price, ")
248 .push("anchored = EXCLUDED.anchored, ")
249 .push("anchorable = EXCLUDED.anchorable, ")
250 .push("fittable_non_singleton = EXCLUDED.fittable_non_singleton, ")
251 .push("published = EXCLUDED.published");
252 query.build().execute(pool).await?;
253 }
254 Ok(())
255 }
256
257 pub async fn update(&self) -> LibResult<()> {
258 let pool = crate::pool();
259 sqlx::query(&format!(
260 "UPDATE {} SET
261 category_id = $2,
262 group_name = $3,
263 icon_id = $4,
264 use_base_price = $5,
265 anchored = $6,
266 fittable_non_singleton = $7,
267 published = $8
268 WHERE group_id = $1",
269 TABLE_NAME
270 ))
271 .bind(self.group_id)
272 .bind(self.category_id)
273 .bind(&self.group_name)
274 .bind(&self.icon_id)
275 .bind(self.use_base_price)
276 .bind(self.anchored)
277 .bind(self.fittable_non_singleton)
278 .bind(self.published)
279 .execute(pool)
280 .await?;
281 Ok(())
282 }
283
284 pub async fn delete(group_id: i32) -> LibResult<()> {
285 let pool = crate::pool();
286 sqlx::query("DELETE FROM item_groups WHERE group_id = $1")
287 .bind(group_id)
288 .execute(pool)
289 .await?;
290 Ok(())
291 }
292}