eversal_lib/database/
inv_group.rs

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    // Parse the group_name query parameter
99    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    // Parse the group_id query parameter
119    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        // Parse the ID as an integer
130        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    // Parse the category_id query parameter
144    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        // Parse the ID as an integer
154        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}