1use super::{
16 Error, JsonSnafu, Model, ModelListParams, Schema, SchemaAllowCreate, SchemaAllowEdit,
17 SchemaType, SchemaView, SqlxSnafu, format_datetime, new_schema_options,
18};
19use super::{REGION_ALIYUN, REGION_ANY, REGION_GZ, REGION_TX};
20use serde::{Deserialize, Serialize};
21use snafu::ResultExt;
22use sqlx::FromRow;
23use sqlx::types::Json;
24use sqlx::{Pool, Postgres, QueryBuilder};
25use time::PrimitiveDateTime;
26
27type Result<T> = std::result::Result<T, Error>;
28
29#[derive(FromRow)]
30struct WebPageDetectorSchema {
31 id: i64,
32 status: i16,
33 name: String,
34 interval: i16,
35 url: String,
36 width: i32,
37 height: i32,
38 user_agent: String,
39 accept_language: String,
40 platform: String,
41 wait_for_element: String,
42 device_scale_factor: f64,
43 timeout: i32,
44 capture_screenshot: bool,
45 capture_element: String,
46 remark: String,
47 regions: Json<Vec<String>>,
48 created_by: i64,
49 created: PrimitiveDateTime,
50 modified: PrimitiveDateTime,
51}
52
53#[derive(Deserialize, Serialize, Clone, Debug)]
54pub struct WebPageDetector {
55 pub id: i64,
56 pub status: i16,
57 pub name: String,
58 pub interval: i16,
59 pub url: String,
60 pub width: i32,
61 pub height: i32,
62 pub user_agent: String,
63 pub accept_language: String,
64 pub platform: String,
65 pub wait_for_element: String,
66 pub device_scale_factor: f64,
67 pub timeout: i32,
68 pub capture_screenshot: bool,
69 pub capture_element: String,
70 pub remark: String,
71 pub regions: Vec<String>,
72 pub created_by: i64,
73 pub created: String,
74 pub modified: String,
75}
76
77impl From<WebPageDetectorSchema> for WebPageDetector {
78 fn from(schema: WebPageDetectorSchema) -> Self {
79 Self {
80 id: schema.id,
81 status: schema.status,
82 name: schema.name,
83 interval: schema.interval,
84 url: schema.url,
85 width: schema.width,
86 height: schema.height,
87 user_agent: schema.user_agent,
88 accept_language: schema.accept_language,
89 platform: schema.platform,
90 wait_for_element: schema.wait_for_element,
91 device_scale_factor: schema.device_scale_factor,
92 timeout: schema.timeout,
93 capture_screenshot: schema.capture_screenshot,
94 capture_element: schema.capture_element,
95 remark: schema.remark,
96 regions: schema.regions.0,
97 created_by: schema.created_by,
98 created: format_datetime(schema.created),
99 modified: format_datetime(schema.modified),
100 }
101 }
102}
103
104#[derive(Deserialize, Serialize, Clone, Debug)]
105pub struct WebPageDetectorInsertParams {
106 pub name: String,
107 pub interval: u16,
108 pub url: String,
109 pub width: u32,
110 pub height: u32,
111 pub user_agent: Option<String>,
112 pub accept_language: Option<String>,
113 pub platform: Option<String>,
114 pub wait_for_element: Option<String>,
115 pub device_scale_factor: Option<f64>,
116 pub timeout: Option<u32>,
117 pub capture_screenshot: Option<bool>,
118 pub capture_element: Option<String>,
119 pub regions: Vec<String>,
120 pub remark: String,
121 pub created_by: u64,
122}
123
124pub struct WebPageDetectorModel {}
125
126impl Model for WebPageDetectorModel {
127 type Output = WebPageDetector;
128 fn new() -> Self {
129 Self {}
130 }
131 fn keyword(&self) -> String {
132 "name".to_string()
133 }
134 async fn schema_view(&self, _pool: &Pool<Postgres>) -> SchemaView {
135 SchemaView {
136 schemas: vec![
137 Schema::new_id(),
138 Schema::new_name(),
139 Schema {
140 name: "interval".to_string(),
141 category: SchemaType::Number,
142 default_value: Some(serde_json::json!(1)),
143 ..Default::default()
144 },
145 Schema {
146 name: "url".to_string(),
147 category: SchemaType::String,
148 required: true,
149 ..Default::default()
150 },
151 Schema {
152 name: "regions".to_string(),
153 category: SchemaType::Strings,
154 options: Some(new_schema_options(&[
155 REGION_ANY,
156 REGION_TX,
157 REGION_GZ,
158 REGION_ALIYUN,
159 ])),
160 ..Default::default()
161 },
162 Schema {
163 name: "width".to_string(),
164 category: SchemaType::Number,
165 ..Default::default()
166 },
167 Schema {
168 name: "height".to_string(),
169 category: SchemaType::Number,
170 ..Default::default()
171 },
172 Schema {
173 name: "user_agent".to_string(),
174 category: SchemaType::String,
175 ..Default::default()
176 },
177 Schema {
178 name: "accept_language".to_string(),
179 category: SchemaType::String,
180 ..Default::default()
181 },
182 Schema {
183 name: "platform".to_string(),
184 category: SchemaType::String,
185 ..Default::default()
186 },
187 Schema {
188 name: "wait_for_element".to_string(),
189 category: SchemaType::String,
190 ..Default::default()
191 },
192 Schema {
193 name: "device_scale_factor".to_string(),
194 category: SchemaType::Number,
195 ..Default::default()
196 },
197 Schema {
198 name: "timeout".to_string(),
199 category: SchemaType::Number,
200 ..Default::default()
201 },
202 Schema {
203 name: "capture_screenshot".to_string(),
204 category: SchemaType::Boolean,
205 ..Default::default()
206 },
207 Schema {
208 name: "capture_element".to_string(),
209 category: SchemaType::String,
210 ..Default::default()
211 },
212 Schema::new_status(),
213 Schema::new_remark(),
214 Schema::new_created(),
215 Schema::new_modified(),
216 ],
217 allow_edit: SchemaAllowEdit {
218 owner: true,
219 roles: vec!["*".to_string()],
220 ..Default::default()
221 },
222 allow_create: SchemaAllowCreate {
223 roles: vec!["*".to_string()],
224 ..Default::default()
225 },
226 }
227 }
228 async fn insert(&self, pool: &Pool<Postgres>, params: serde_json::Value) -> Result<u64> {
229 let params: WebPageDetectorInsertParams =
230 serde_json::from_value(params).context(JsonSnafu)?;
231 let row: (i64,) = sqlx::query_as(
232 r#"INSERT INTO web_page_detectors (name, "interval", url, width, height, user_agent, accept_language, platform, wait_for_element, device_scale_factor, timeout, capture_screenshot, capture_element, remark, regions, created_by) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING id"#,
233 )
234 .bind(params.name)
235 .bind(params.interval as i16)
236 .bind(params.url)
237 .bind(params.width as i32)
238 .bind(params.height as i32)
239 .bind(params.user_agent.unwrap_or_default())
240 .bind(params.accept_language.unwrap_or_default())
241 .bind(params.platform.unwrap_or_default())
242 .bind(params.wait_for_element.unwrap_or_default())
243 .bind(params.device_scale_factor.unwrap_or_default())
244 .bind(params.timeout.unwrap_or_default() as i32)
245 .bind(params.capture_screenshot.unwrap_or_default())
246 .bind(params.capture_element.unwrap_or_default())
247 .bind(params.remark)
248 .bind(Json(params.regions))
249 .bind(params.created_by as i64)
250 .fetch_one(pool)
251 .await
252 .context(SqlxSnafu)?;
253
254 Ok(row.0 as u64)
255 }
256 async fn count(&self, pool: &Pool<Postgres>, params: &ModelListParams) -> Result<i64> {
257 let mut qb = QueryBuilder::new("SELECT COUNT(*) FROM web_page_detectors");
258 self.push_conditions(&mut qb, params)?;
259 let count = qb
260 .build_query_scalar::<i64>()
261 .fetch_one(pool)
262 .await
263 .context(SqlxSnafu)?;
264 Ok(count)
265 }
266 async fn list(
267 &self,
268 pool: &Pool<Postgres>,
269 params: &ModelListParams,
270 ) -> Result<Vec<Self::Output>> {
271 let mut qb = QueryBuilder::new("SELECT * FROM web_page_detectors");
272 self.push_conditions(&mut qb, params)?;
273 params.push_pagination(&mut qb);
274 let detectors = qb
275 .build_query_as::<WebPageDetectorSchema>()
276 .fetch_all(pool)
277 .await
278 .context(SqlxSnafu)?;
279 Ok(detectors.into_iter().map(|s| s.into()).collect())
280 }
281}
282
283impl WebPageDetectorModel {
284 pub async fn list_enabled_by_region(
285 &self,
286 pool: &Pool<Postgres>,
287 region: Option<String>,
288 limit: u64,
289 offset: u64,
290 ) -> Result<Vec<WebPageDetector>> {
291 let region = region.unwrap_or(REGION_ANY.to_string());
292 let detectors = sqlx::query_as::<_, WebPageDetectorSchema>(
293 r#"SELECT * FROM web_page_detectors WHERE deleted_at IS NULL AND status = 1 AND (jsonb_array_length(regions) = 0 OR regions @> $1::jsonb OR regions @> $2::jsonb) ORDER BY id ASC LIMIT $3 OFFSET $4"#,
294 )
295 .bind(format!("[{:?}]", region))
296 .bind(format!("[{:?}]", REGION_ANY))
297 .bind(limit as i64)
298 .bind(offset as i64)
299 .fetch_all(pool)
300 .await
301 .context(SqlxSnafu)?;
302
303 Ok(detectors.into_iter().map(|schema| schema.into()).collect())
304 }
305}