eversal_lib/database/
inv_type.rs

1use sqlx::{Postgres, QueryBuilder};
2
3use crate::{LibResult, Error};
4
5const TABLE_NAME: &str = "inv_types";
6
7#[derive(Debug, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
8pub struct TypeDb {
9  #[serde(rename = "typeId")]
10  pub type_id: i32,
11  #[serde(rename = "groupId")]
12  pub group_id: i32,
13  #[serde(rename = "typeName")]
14  pub type_name: String,
15  #[serde(rename = "description", skip_serializing_if = "Option::is_none")]
16  pub description: Option<String>,
17  pub mass: f32,
18  pub volume: f32,
19  pub capacity: f32,
20  #[serde(rename = "portionSize")]
21  pub portion_size: i32,
22  #[serde(rename = "raceId", skip_serializing_if = "Option::is_none")]
23  pub race_id: Option<i32>,
24  #[serde(rename = "basePrice", skip_serializing_if = "Option::is_none")]
25  pub base_price: Option<f32>,
26  pub published: bool,
27  #[serde(rename = "marketGroupId", skip_serializing_if = "Option::is_none")]
28  pub market_group_id: Option<i32>,
29  #[serde(rename = "iconId", skip_serializing_if = "Option::is_none")]
30  pub icon_id: Option<i32>,
31  #[serde(rename = "soundId", skip_serializing_if = "Option::is_none")]
32  pub sound_id: Option<i32>,
33  #[serde(rename = "graphicId", skip_serializing_if = "Option::is_none")]
34  pub graphic_id: Option<i32>,
35}
36
37impl TypeDb {
38  pub async fn get_by_id(type_id: i32) -> LibResult<Self> {
39    let pool = crate::pool();
40    let item =
41      sqlx::query_as::<_, Self>(&format!("SELECT * FROM {} WHERE type_id = $1", TABLE_NAME))
42        .bind(type_id)
43        .fetch_one(pool)
44        .await?;
45
46    Ok(item)
47  }
48
49  pub async fn get_by_names(type_names: Vec<String>) -> LibResult<Vec<Self>> {
50    let pool = crate::pool();
51    let mut query = QueryBuilder::new(format!("SELECT * FROM {}", TABLE_NAME));
52    query.push(" WHERE");
53    // Do a && overlaps query
54    query.push(" LOWER(type_name) IN (");
55    for (i, type_name) in type_names.iter().enumerate() {
56      if i > 0 {
57        query.push(", ");
58      }
59      query.push_bind(type_name.to_lowercase());
60    }
61    query.push(")");
62    let query = query.build_query_as();
63    match query.fetch_all(pool).await {
64      Ok(items) => Ok(items),
65      Err(err) => {
66        log::error!("Error fetching items: {}", err);
67        return Err(Error::new(500, format!("Error fetching items: {}", err)));
68      }
69    }
70  }
71
72  pub async fn count(filter: &TypeFilter) -> LibResult<i64> {
73    let pool = crate::pool();
74    let mut query = QueryBuilder::new(format!("SELECT COUNT(*) FROM {}", TABLE_NAME));
75    query = Self::build_query(query, filter);
76    let query = query.build_query_scalar();
77
78    match query.fetch_one(pool).await {
79      Ok(count) => Ok(count),
80      Err(err) => {
81        log::error!("Error counting items: {}", err);
82        return Err(Error::new(500, format!("Error counting items: {}", err)));
83      }
84    }
85  }
86
87  pub async fn get_multiple(filter: &TypeFilter) -> LibResult<Vec<Self>> {
88    let page = filter.page.unwrap_or(1);
89    let limit = filter.limit.unwrap_or(100);
90
91    let pool = crate::pool();
92    let mut query = QueryBuilder::new(format!("SELECT * FROM {}", TABLE_NAME));
93    query = Self::build_query(query, filter);
94    query.push(" ORDER BY type_id LIMIT ");
95    query.push_bind(limit);
96    query.push(" OFFSET ");
97    query.push_bind((page - 1) * limit);
98    let query = query.build_query_as();
99
100    match query.fetch_all(pool).await {
101      Ok(items) => Ok(items),
102      Err(err) => {
103        log::error!("Error fetching items: {}", err);
104        return Err(Error::new(500, format!("Error fetching items: {}", err)));
105      }
106    }
107  }
108
109  fn build_query<'a>(
110    mut query: QueryBuilder<'a, Postgres>,
111    filter: &'a TypeFilter,
112  ) -> QueryBuilder<'a, Postgres> {
113    let mut has_where = false;
114
115    // Parse the type_name query parameter
116    if let Some(type_names) = &filter.type_name {
117      if !has_where {
118        query.push(" WHERE");
119        has_where = true;
120      } else {
121        query.push(" AND");
122      }
123      query.push(" (");
124      for (i, type_name) in type_names.iter().enumerate() {
125        if i > 0 {
126          query.push(" OR");
127        }
128        query.push(" type_name ILIKE ");
129        query.push_bind(format!("%{}%", type_name));
130      }
131      query.push(" )");
132    }
133
134    // Parse the type_id query parameter
135    if let Some(ids) = &filter.type_id {
136      if !has_where {
137        query.push(" WHERE");
138        has_where = true;
139      } else {
140        query.push(" AND");
141      }
142      query.push(" (");
143      for (i, type_id) in ids.iter().enumerate() {
144        if i > 0 {
145          query.push(" OR");
146        }
147        query.push(" type_id = ");
148        query.push_bind(type_id);
149      }
150      query.push(" )");
151    }
152
153    // Parse the group_id query parameter
154    if let Some(group_ids) = &filter.group_id {
155      if !has_where {
156        query.push(" WHERE");
157      } else {
158        query.push(" AND");
159      }
160      query.push(" (");
161      for (i, group_id) in group_ids.iter().enumerate() {
162        if i > 0 {
163          query.push(" OR");
164        }
165        query.push(" group_id = ");
166        query.push_bind(group_id);
167      }
168      query.push(" )");
169    }
170
171    query
172  }
173
174  pub async fn insert(&self) -> LibResult<()> {
175    let pool = crate::pool();
176    sqlx::query(&format!(
177      "INSERT INTO {} (
178        type_id,
179        group_id,
180        type_name,
181        description,
182        mass,
183        volume,
184        capacity,
185        portion_size,
186        race_id,
187        base_price,
188        published,
189        market_group_id,
190        icon_id,
191        sound_id,
192        graphic_id
193      ) VALUES (
194        $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15
195      )",
196      TABLE_NAME
197    ))
198    .bind(self.type_id)
199    .bind(self.group_id)
200    .bind(&self.type_name)
201    .bind(&self.description)
202    .bind(self.mass)
203    .bind(self.volume)
204    .bind(self.capacity)
205    .bind(self.portion_size)
206    .bind(self.race_id)
207    .bind(self.base_price)
208    .bind(self.published)
209    .bind(self.market_group_id)
210    .bind(self.icon_id)
211    .bind(self.sound_id)
212    .bind(self.graphic_id)
213    .execute(pool)
214    .await?;
215    Ok(())
216  }
217
218  pub async fn insert_multiple(types: &Vec<Self>) -> LibResult<()> {
219    let pool = crate::pool();
220    let step = 1000;
221    for i in (0..types.len()).step_by(step) {
222      let mut query = sqlx::QueryBuilder::new(format!(
223        r#"
224        INSERT INTO {} (type_id, group_id, type_name, description, mass, volume, capacity, 
225        portion_size, race_id, base_price, published, market_group_id, icon_id, sound_id, graphic_id)
226        VALUES
227        "#,
228        TABLE_NAME
229      ));
230
231      for j in 0..step {
232        if i + j >= types.len() {
233          break;
234        }
235        if j > 0 {
236          query.push(", ");
237        }
238        let type_ = &types[i + j];
239        query
240          .push(" (")
241          .push_bind(type_.type_id)
242          .push(", ")
243          .push_bind(type_.group_id)
244          .push(", ")
245          .push_bind(&type_.type_name)
246          .push(", ")
247          .push_bind(&type_.description)
248          .push(", ")
249          .push_bind(type_.mass)
250          .push(", ")
251          .push_bind(type_.volume)
252          .push(", ")
253          .push_bind(type_.capacity)
254          .push(", ")
255          .push_bind(type_.portion_size)
256          .push(", ")
257          .push_bind(type_.race_id)
258          .push(", ")
259          .push_bind(type_.base_price)
260          .push(", ")
261          .push_bind(type_.published)
262          .push(", ")
263          .push_bind(type_.market_group_id)
264          .push(", ")
265          .push_bind(type_.icon_id)
266          .push(", ")
267          .push_bind(type_.sound_id)
268          .push(", ")
269          .push_bind(type_.graphic_id)
270          .push(") ");
271      }
272      query
273        .push("ON CONFLICT (type_id) DO UPDATE SET ")
274        .push("group_id = EXCLUDED.group_id, ")
275        .push("type_name = EXCLUDED.type_name, ")
276        .push("description = EXCLUDED.description, ")
277        .push("mass = EXCLUDED.mass, ")
278        .push("volume = EXCLUDED.volume, ")
279        .push("capacity = EXCLUDED.capacity, ")
280        .push("portion_size = EXCLUDED.portion_size, ")
281        .push("race_id = EXCLUDED.race_id, ")
282        .push("base_price = EXCLUDED.base_price, ")
283        .push("published = EXCLUDED.published, ")
284        .push("market_group_id = EXCLUDED.market_group_id, ")
285        .push("icon_id = EXCLUDED.icon_id, ")
286        .push("sound_id = EXCLUDED.sound_id, ")
287        .push("graphic_id = EXCLUDED.graphic_id");
288      query.build().execute(pool).await?;
289    }
290    Ok(())
291  }
292
293  pub async fn update(&self) -> LibResult<()> {
294    let pool = crate::pool();
295    sqlx::query(
296      "UPDATE items SET
297        group_id = $2,
298        type_name = $3,
299        description = $4,
300        mass = $5,
301        volume = $6,
302        capacity = $7,
303        portion_size = $8,
304        race_id = $9,
305        base_price = $10,
306        published = $11,
307        market_group_id = $12,
308        icon_id = $13,
309        sound_id = $14,
310        graphic_id = $15
311      WHERE type_id = $1",
312    )
313    .bind(self.type_id)
314    .bind(self.group_id)
315    .bind(&self.type_name)
316    .bind(&self.description)
317    .bind(self.mass)
318    .bind(self.volume)
319    .bind(self.capacity)
320    .bind(self.portion_size)
321    .bind(self.race_id)
322    .bind(self.base_price)
323    .bind(self.published)
324    .bind(self.market_group_id)
325    .bind(self.icon_id)
326    .bind(self.sound_id)
327    .bind(self.graphic_id)
328    .execute(pool)
329    .await?;
330    Ok(())
331  }
332
333  pub async fn delete(type_id: i32) -> LibResult<()> {
334    let pool = crate::pool();
335    sqlx::query(&format!("DELETE FROM {} WHERE type_id = $1", TABLE_NAME))
336      .bind(type_id)
337      .execute(pool)
338      .await?;
339    Ok(())
340  }
341}
342
343#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
344pub struct TypeFilter {
345  #[serde(rename = "typeID")]
346  pub type_id: Option<Vec<i32>>,
347  #[serde(rename = "typeName")]
348  pub type_name: Option<Vec<String>>,
349  #[serde(rename = "groupID")]
350  pub group_id: Option<Vec<i32>>,
351  pub page: Option<i64>,
352  pub limit: Option<i64>,
353}