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 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 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 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 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}