eversal_lib/database/
inv_category.rs

1use sqlx::{Postgres, QueryBuilder};
2
3use crate::{LibResult, Error};
4
5const TABLE_NAME: &str = "inv_categories";
6
7#[derive(Debug, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
8pub struct CategoryDb {
9  #[serde(rename = "categoryID")]
10  pub category_id: i32,
11  #[serde(rename = "categoryName")]
12  pub category_name: String,
13  #[serde(rename = "iconID")]
14  pub icon_id: Option<i32>,
15  pub published: bool,
16}
17
18#[derive(Debug, serde::Serialize, serde::Deserialize)]
19pub struct CategoryFilter {
20  pub category_id: Option<Vec<i32>>,
21  pub category_name: Option<Vec<String>>,
22  pub page: Option<i64>,
23  pub limit: Option<i64>,
24}
25
26impl CategoryDb {
27  /**
28   * Get a category by its ID.
29   */
30  pub async fn get_by_id(category_id: i32) -> LibResult<Self> {
31    let pool = crate::pool();
32    let category = sqlx::query_as::<_, Self>(&format!(
33      r#"
34      SELECT * FROM {}
35      WHERE category_id = $1
36      "#,
37      TABLE_NAME
38    ))
39    .bind(category_id)
40    .fetch_one(pool)
41    .await?;
42
43    Ok(category)
44  }
45
46  /**
47   * Count the number of categories in the database.
48   */
49  pub async fn count(filter: &CategoryFilter) -> LibResult<i64> {
50    let pool = crate::pool();
51    let mut query = QueryBuilder::new(format!("SELECT COUNT(*) FROM {}", TABLE_NAME));
52    query = Self::build_query(query, filter);
53    let query = query.build_query_scalar();
54
55    match query.fetch_one(pool).await {
56      Ok(count) => Ok(count),
57      Err(err) => {
58        log::error!("Error counting items: {}", err);
59        return Err(Error::new(500, format!("Error counting items: {}", err)));
60      }
61    }
62  }
63
64  /**
65   * Fetch categories from the database.
66   */
67  pub async fn get_multiple(params: &CategoryFilter) -> LibResult<Vec<Self>> {
68    let page = params.page.unwrap_or(1);
69    let limit = params.limit.unwrap_or(100);
70
71    let pool = crate::pool();
72    let mut query = QueryBuilder::new(format!("SELECT * FROM {}", TABLE_NAME));
73    query = Self::build_query(query, params);
74    query.push(" ORDER BY category_id LIMIT ");
75    query.push_bind(limit);
76    query.push(" OFFSET ");
77    query.push_bind((page - 1) * limit);
78    let query = query.build_query_as();
79
80    match query.fetch_all(pool).await {
81      Ok(items) => Ok(items),
82      Err(err) => {
83        log::error!("Error fetching categories: {}", err);
84        return Err(Error::new(
85          500,
86          format!("Error fetching categories: {}", err),
87        ));
88      }
89    }
90  }
91
92  /**
93   * Insert a category into the database.
94   */
95  pub async fn insert(&self) -> LibResult<()> {
96    let pool = crate::pool();
97    sqlx::query(&format!(
98      "INSERT INTO {} (
99        category_id,
100        category_name,
101        published
102      ) VALUES (
103        $1, $2, $3
104      )",
105      TABLE_NAME
106    ))
107    .bind(self.category_id)
108    .bind(&self.category_name)
109    .bind(&self.published)
110    .execute(pool)
111    .await?;
112    Ok(())
113  }
114
115  /**
116   * Insert multiple categories into the database.
117   */
118  pub async fn insert_multiple(categories: Vec<Self>) -> LibResult<()> {
119    let pool = crate::pool();
120    let step = 1000;
121    for i in (0..categories.len()).step_by(step) {
122      let mut query = sqlx::QueryBuilder::new(format!(
123        r#"
124        INSERT INTO {} (category_id, category_name, icon_id, published)
125        VALUES
126        "#,
127        TABLE_NAME
128      ));
129
130      for j in 0..step {
131        if i + j >= categories.len() {
132          break;
133        }
134        if j > 0 {
135          query.push(", ");
136        }
137        let category = &categories[i + j];
138        query
139          .push(" (")
140          .push_bind(category.category_id)
141          .push(", ")
142          .push_bind(&category.category_name)
143          .push(", ")
144          .push_bind(category.icon_id)
145          .push(", ")
146          .push_bind(category.published)
147          .push(") ");
148      }
149      query
150        .push("ON CONFLICT (category_id) DO UPDATE SET ")
151        .push("category_name = EXCLUDED.category_name, ")
152        .push("icon_id = EXCLUDED.icon_id, ")
153        .push("published = EXCLUDED.published");
154      query.build().execute(pool).await?;
155    }
156    Ok(())
157  }
158
159  /**
160   * Update a category in the database.
161   */
162  pub async fn update(&self) -> LibResult<()> {
163    let pool = crate::pool();
164    sqlx::query(&format!(
165      "UPDATE {} SET
166        category_name = $2,
167        published = $3
168      WHERE category_id = $1",
169      TABLE_NAME
170    ))
171    .bind(self.category_id)
172    .bind(&self.category_name)
173    .bind(&self.published)
174    .execute(pool)
175    .await?;
176    Ok(())
177  }
178
179  /**
180   * Delete a category from the database.
181   */
182  pub async fn delete(category_id: i32) -> LibResult<()> {
183    let pool = crate::pool();
184    sqlx::query(&format!(
185      "DELETE FROM {} WHERE category_id = $1",
186      TABLE_NAME
187    ))
188    .bind(category_id)
189    .execute(pool)
190    .await?;
191    Ok(())
192  }
193
194  /**
195   * Build the query for fetching categories.
196   */
197  fn build_query<'a>(
198    mut query: QueryBuilder<'a, Postgres>,
199    filter: &'a CategoryFilter,
200  ) -> QueryBuilder<'a, Postgres> {
201    let mut has_where = false;
202
203    // Parse the category_name query parameter
204    if let Some(category_names) = &filter.category_name {
205      if !has_where {
206        query.push(" WHERE");
207        has_where = true;
208      } else {
209        query.push(" AND");
210      }
211      query.push(" (");
212      for (i, category_name) in category_names.iter().enumerate() {
213        if i > 0 {
214          query.push(" OR");
215        }
216        query.push(" category_name ILIKE ");
217        query.push_bind(format!("%{}%", category_name));
218      }
219      query.push(" )");
220    }
221
222    // Parse the category_id query parameter
223    if let Some(ids) = &filter.category_id {
224      if !has_where {
225        query.push(" WHERE");
226      } else {
227        query.push(" AND");
228      }
229      query.push(" (");
230      for (i, category_id) in ids.iter().enumerate() {
231        if i > 0 {
232          query.push(" OR");
233        }
234        query.push(" category_id = ");
235        query.push_bind(category_id);
236      }
237      query.push(" )");
238    }
239
240    query
241  }
242}