1use sqlx::{Postgres, QueryBuilder};
2
3use crate::{LibResult, Error};
4
5const TABLE_NAME: &str = "map_regions";
6
7#[derive(Debug, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
8pub struct RegionDb {
9 #[serde(rename = "regionId")]
10 pub region_id: i32,
11 #[serde(rename = "regionName")]
12 pub region_name: String,
13 pub x: f32,
14 pub y: f32,
15 pub z: f32,
16 #[serde(rename = "xMin")]
17 pub x_min: f32,
18 #[serde(rename = "xMax")]
19 pub x_max: f32,
20 #[serde(rename = "yMin")]
21 pub y_min: f32,
22 #[serde(rename = "yMax")]
23 pub y_max: f32,
24 #[serde(rename = "zMin")]
25 pub z_min: f32,
26 #[serde(rename = "zMax")]
27 pub z_max: f32,
28 #[serde(rename = "factionId", skip_serializing_if = "Option::is_none")]
29 pub faction_id: Option<i32>,
30 pub nebula: i32,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub radius: Option<f32>,
33}
34
35#[derive(Debug, serde::Serialize, serde::Deserialize)]
36pub struct RegionFilter {
37 #[serde(rename = "regionId")]
38 pub region_id: Option<Vec<i32>>,
39 #[serde(rename = "regionName")]
40 pub region_name: Option<Vec<String>>,
41 pub include_extra: Option<bool>,
42 pub page: Option<i64>,
43 pub limit: Option<i64>,
44}
45
46impl RegionDb {
47 pub async fn get_by_id(region_id: i32) -> LibResult<Option<Self>> {
48 let pool = crate::pool();
49 let item = sqlx::query_as::<_, Self>(&format!(
50 "SELECT * FROM {} WHERE region_id = $1",
51 TABLE_NAME
52 ))
53 .bind(region_id)
54 .fetch_optional(pool)
55 .await?;
56 Ok(item)
57 }
58
59 pub async fn count(filter: &RegionFilter) -> LibResult<i64> {
60 let pool = crate::pool();
61 let mut query = QueryBuilder::new(format!("SELECT COUNT(*) FROM {}", TABLE_NAME));
62 query = Self::build_query(query, filter);
63 let query = query.build_query_scalar();
64
65 match query.fetch_one(pool).await {
66 Ok(count) => Ok(count),
67 Err(err) => {
68 log::error!("Error counting items: {}", err);
69 return Err(Error::new(500, format!("Error counting items: {}", err)));
70 }
71 }
72 }
73
74 pub async fn get(filter: &RegionFilter) -> LibResult<Vec<Self>> {
75 let page = filter.page.unwrap_or(1);
76 let limit = filter.limit.unwrap_or(100);
77
78 let pool = crate::pool();
79 let mut query = QueryBuilder::new(format!("SELECT * FROM {}", TABLE_NAME));
80 query = Self::build_query(query, filter);
81 query.push(" ORDER BY region_id LIMIT ");
82 query.push_bind(limit);
83 query.push(" OFFSET ");
84 query.push_bind((page - 1) * limit);
85 let query = query.build_query_as();
86
87 match query.fetch_all(pool).await {
88 Ok(items) => Ok(items),
89 Err(err) => {
90 log::error!("Error fetching items: {}", err);
91 return Err(Error::new(500, format!("Error fetching items: {}", err)));
92 }
93 }
94 }
95
96 fn build_query<'a>(
97 mut query: QueryBuilder<'a, Postgres>,
98 filter: &'a RegionFilter,
99 ) -> QueryBuilder<'a, Postgres> {
100 let mut has_where = false;
101
102 if let Some(region_names) = &filter.region_name {
104 if !has_where {
105 query.push(" WHERE");
106 has_where = true;
107 } else {
108 query.push(" AND");
109 }
110 query.push(" (");
111 for (i, region_name) in region_names.iter().enumerate() {
112 if i > 0 {
113 query.push(" OR");
114 }
115 query.push(" region_name ILIKE ");
116 query.push_bind(format!("%{}%", region_name));
117 }
118 query.push(" )");
119 }
120
121 if let Some(region_ids) = &filter.region_id {
123 if !has_where {
124 query.push(" WHERE");
125 has_where = true;
126 } else {
127 query.push(" AND");
128 }
129 query.push(" (");
130 for (i, region_id) in region_ids.iter().enumerate() {
131 if i > 0 {
132 query.push(" OR");
133 }
134 query.push(" region_id = ");
135 query.push_bind(region_id);
136 }
137 query.push(" )");
138 }
139
140 let include_extra = filter.include_extra.unwrap_or(false);
142 if !include_extra {
143 if !has_where {
144 query.push(" WHERE");
145 } else {
146 query.push(" AND");
147 }
148 query.push(" region_id < 11000000");
150 }
151
152 query
153 }
154
155 pub async fn insert(&self) -> LibResult<()> {
156 let pool = crate::pool();
157 sqlx::query(&format!(
158 "INSERT INTO {} (
159 region_id,
160 region_name,
161 x,
162 y,
163 z,
164 x_min,
165 x_max,
166 y_min,
167 y_max,
168 z_min,
169 z_max,
170 faction_id,
171 nebula,
172 radius
173 ) VALUES (
174 $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14
175 )",
176 TABLE_NAME
177 ))
178 .bind(self.region_id)
179 .bind(&self.region_name)
180 .bind(self.x)
181 .bind(self.y)
182 .bind(self.z)
183 .bind(self.x_min)
184 .bind(self.x_max)
185 .bind(self.y_min)
186 .bind(self.y_max)
187 .bind(self.z_min)
188 .bind(self.z_max)
189 .bind(self.faction_id)
190 .bind(self.nebula)
191 .bind(self.radius)
192 .execute(pool)
193 .await?;
194 Ok(())
195 }
196
197 pub async fn insert_multiple(regions: &Vec<RegionDb>) -> LibResult<()> {
198 let pool = crate::pool();
199 let step = 1000;
200 for i in (0..regions.len()).step_by(step) {
201 let mut query = sqlx::QueryBuilder::new(format!(
202 r#"
203 INSERT INTO {} (region_id, region_name, x, y, z, x_min, x_max, y_min, y_max, z_min, z_max,
204 faction_id, nebula, radius)
205 VALUES
206 "#,
207 TABLE_NAME
208 ));
209
210 for j in 0..step {
211 if i + j >= regions.len() {
212 break;
213 }
214 if j > 0 {
215 query.push(", ");
216 }
217 let region = ®ions[i + j];
218 query
219 .push(" (")
220 .push_bind(region.region_id)
221 .push(", ")
222 .push_bind(®ion.region_name)
223 .push(", ")
224 .push_bind(region.x)
225 .push(", ")
226 .push_bind(region.y)
227 .push(", ")
228 .push_bind(region.z)
229 .push(", ")
230 .push_bind(region.x_min)
231 .push(", ")
232 .push_bind(region.x_max)
233 .push(", ")
234 .push_bind(region.y_min)
235 .push(", ")
236 .push_bind(region.y_max)
237 .push(", ")
238 .push_bind(region.z_min)
239 .push(", ")
240 .push_bind(region.z_max)
241 .push(", ")
242 .push_bind(region.faction_id)
243 .push(", ")
244 .push_bind(region.nebula)
245 .push(", ")
246 .push_bind(region.radius)
247 .push(") ");
248 }
249
250 query
251 .push("ON CONFLICT (region_id) DO UPDATE SET ")
252 .push("region_name = EXCLUDED.region_name, ")
253 .push("x = EXCLUDED.x, ")
254 .push("y = EXCLUDED.y, ")
255 .push("z = EXCLUDED.z, ")
256 .push("x_min = EXCLUDED.x_min, ")
257 .push("x_max = EXCLUDED.x_max, ")
258 .push("y_min = EXCLUDED.y_min, ")
259 .push("y_max = EXCLUDED.y_max, ")
260 .push("z_min = EXCLUDED.z_min, ")
261 .push("z_max = EXCLUDED.z_max, ")
262 .push("faction_id = EXCLUDED.faction_id, ")
263 .push("nebula = EXCLUDED.nebula, ")
264 .push("radius = EXCLUDED.radius");
265 query.build().execute(pool).await?;
266 }
267 Ok(())
268 }
269
270 pub async fn update(&self) -> LibResult<()> {
271 let pool = crate::pool();
272 sqlx::query(&format!(
273 "UPDATE {} SET
274 region_name = $2,
275 x = $3,
276 y = $4,
277 z = $5,
278 x_min = $6,
279 x_max = $7,
280 y_min = $8,
281 y_max = $9,
282 z_min = $10,
283 z_max = $11,
284 faction_id = $12,
285 nebula = $13,
286 radius = $14
287 WHERE region_id = $1",
288 TABLE_NAME
289 ))
290 .bind(self.region_id)
291 .bind(&self.region_name)
292 .bind(self.x)
293 .bind(self.y)
294 .bind(self.z)
295 .bind(self.x_min)
296 .bind(self.x_max)
297 .bind(self.y_min)
298 .bind(self.y_max)
299 .bind(self.z_min)
300 .bind(self.z_max)
301 .bind(self.faction_id)
302 .bind(self.nebula)
303 .bind(self.radius)
304 .execute(pool)
305 .await?;
306 Ok(())
307 }
308
309 pub async fn delete(region_id: i64) -> LibResult<()> {
310 let pool = crate::pool();
311 sqlx::query(&format!("DELETE FROM {} WHERE region_id = $1", TABLE_NAME))
312 .bind(region_id)
313 .execute(pool)
314 .await?;
315 Ok(())
316 }
317}