Skip to main content

tibba_model_builtin/
detector_group_user.rs

1// Copyright 2026 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::DetectorGroupModel;
16use serde::{Deserialize, Serialize};
17use snafu::ResultExt;
18use sqlx::FromRow;
19use sqlx::{Pool, Postgres, QueryBuilder};
20use std::collections::HashMap;
21use tibba_model::{
22    Error, JsonSnafu, Model, ModelListParams, Schema, SchemaAllowCreate, SchemaAllowEdit,
23    SchemaOption, SchemaOptionValue, SchemaType, SchemaView, SqlxSnafu, format_datetime,
24    parse_primitive_datetime,
25};
26use time::PrimitiveDateTime;
27
28type Result<T> = std::result::Result<T, Error>;
29
30// 在相应的模型文件中定义
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum DetectorGroupRole {
33    Owner = 1,
34    Admin = 2,
35    Member = 3,
36    Viewer = 4,
37}
38
39impl DetectorGroupRole {
40    // pub fn from_i8(value: i8) -> Option<Self> {
41    //     match value {
42    //         1 => Some(Self::Owner),
43    //         2 => Some(Self::Admin),
44    //         3 => Some(Self::Member),
45    //         4 => Some(Self::Viewer),
46    //         _ => None,
47    //     }
48    // }
49
50    pub fn to_i16(self) -> i16 {
51        self as i16
52    }
53
54    pub fn as_str(self) -> &'static str {
55        match self {
56            Self::Owner => "owner",
57            Self::Admin => "admin",
58            Self::Member => "member",
59            Self::Viewer => "viewer",
60        }
61    }
62}
63
64impl std::fmt::Display for DetectorGroupRole {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        write!(f, "{}", self.as_str())
67    }
68}
69
70#[derive(FromRow)]
71struct DetectorGroupUserSchema {
72    id: i64,
73    user_id: i64,
74    group_id: i64,
75    role: i16,
76    status: i16,
77    effective_start_time: PrimitiveDateTime,
78    effective_end_time: PrimitiveDateTime,
79    invited_by: i64,
80    remark: String,
81    created: PrimitiveDateTime,
82    modified: PrimitiveDateTime,
83}
84
85#[derive(Deserialize, Serialize, Clone, Debug)]
86pub struct DetectorGroupUser {
87    pub id: i64,
88    pub user_id: i64,
89    pub group_id: i64,
90    pub role: i16,
91    pub status: i16,
92    pub effective_start_time: String,
93    pub effective_end_time: String,
94    pub invited_by: i64,
95    pub remark: String,
96    pub created: String,
97    pub modified: String,
98}
99
100impl From<DetectorGroupUserSchema> for DetectorGroupUser {
101    fn from(schema: DetectorGroupUserSchema) -> Self {
102        Self {
103            id: schema.id,
104            user_id: schema.user_id,
105            group_id: schema.group_id,
106            role: schema.role,
107            status: schema.status,
108            effective_start_time: format_datetime(schema.effective_start_time),
109            effective_end_time: format_datetime(schema.effective_end_time),
110            invited_by: schema.invited_by,
111            remark: schema.remark,
112            created: format_datetime(schema.created),
113            modified: format_datetime(schema.modified),
114        }
115    }
116}
117
118#[derive(Deserialize, Serialize, Clone, Debug)]
119pub struct DetectorGroupUserInsertParams {
120    pub user_id: u64,
121    pub group_id: u64,
122    pub role: i16,
123    pub status: i16,
124    pub effective_start_time: String,
125    pub effective_end_time: String,
126    pub invited_by: u64,
127    pub created_by: u64,
128    pub remark: String,
129}
130
131#[derive(Deserialize, Serialize, Clone, Debug)]
132pub struct DetectorGroupUserUpdateParams {
133    pub role: Option<i16>,
134    pub status: Option<i16>,
135    pub effective_start_time: Option<String>,
136    pub effective_end_time: Option<String>,
137    pub invited_by: Option<u64>,
138    pub remark: Option<String>,
139}
140
141pub struct DetectorGroupUserModel {}
142
143impl Model for DetectorGroupUserModel {
144    type Output = DetectorGroupUser;
145    fn new() -> Self {
146        Self {}
147    }
148    async fn schema_view(&self, pool: &Pool<Postgres>) -> SchemaView {
149        let mut group_options = vec![];
150        if let Ok(groups) = DetectorGroupModel::new().list_enabled(pool).await {
151            for group in groups {
152                group_options.push(SchemaOption {
153                    label: group.name,
154                    value: SchemaOptionValue::Integer(group.id),
155                });
156            }
157            group_options.sort_by_key(|option| option.label.clone());
158        }
159        SchemaView {
160            schemas: vec![
161                Schema::new_id(),
162                Schema::new_user_search("user_id"),
163                Schema {
164                    name: "group_id".to_string(),
165                    category: SchemaType::Number,
166                    required: true,
167                    options: Some(group_options),
168                    ..Default::default()
169                },
170                Schema {
171                    name: "role".to_string(),
172                    category: SchemaType::Number,
173                    options: Some(vec![
174                        SchemaOption {
175                            label: DetectorGroupRole::Owner.to_string(),
176                            value: SchemaOptionValue::Integer(
177                                DetectorGroupRole::Owner.to_i16() as i64
178                            ),
179                        },
180                        SchemaOption {
181                            label: DetectorGroupRole::Admin.to_string(),
182                            value: SchemaOptionValue::Integer(
183                                DetectorGroupRole::Admin.to_i16() as i64
184                            ),
185                        },
186                        SchemaOption {
187                            label: DetectorGroupRole::Member.to_string(),
188                            value: SchemaOptionValue::Integer(
189                                DetectorGroupRole::Member.to_i16() as i64
190                            ),
191                        },
192                        SchemaOption {
193                            label: DetectorGroupRole::Viewer.to_string(),
194                            value: SchemaOptionValue::Integer(
195                                DetectorGroupRole::Viewer.to_i16() as i64
196                            ),
197                        },
198                    ]),
199                    ..Default::default()
200                },
201                Schema::new_status(),
202                Schema::new_effective_start_time(),
203                Schema::new_effective_end_time(),
204                Schema::new_remark(),
205                Schema::new_created(),
206            ],
207            allow_edit: SchemaAllowEdit {
208                owner: true,
209                roles: vec!["*".to_string()],
210                ..Default::default()
211            },
212            allow_create: SchemaAllowCreate {
213                roles: vec!["*".to_string()],
214                ..Default::default()
215            },
216        }
217    }
218
219    fn push_filter_conditions<'args>(
220        &self,
221        qb: &mut QueryBuilder<'args, Postgres>,
222        filters: &HashMap<String, String>,
223    ) -> Result<()> {
224        if let Some(status) = filters.get("status").and_then(|s| s.parse::<i16>().ok()) {
225            qb.push(" AND status = ");
226            qb.push_bind(status);
227        }
228        if let Some(group_id) = filters.get("group_id").and_then(|s| s.parse::<i64>().ok()) {
229            qb.push(" AND group_id = ");
230            qb.push_bind(group_id);
231        }
232        Ok(())
233    }
234
235    async fn insert(&self, pool: &Pool<Postgres>, mut params: serde_json::Value) -> Result<u64> {
236        // invited_by 未指定时默认与 created_by 相同
237        // user_id 支持前端以字符串形式传入(JS 大整数安全)
238        if let Some(obj) = params.as_object_mut() {
239            if !obj.contains_key("invited_by") {
240                if let Some(created_by) = obj.get("created_by").cloned() {
241                    obj.insert("invited_by".to_string(), created_by);
242                }
243            }
244            if let Some(id_str) = obj.get("user_id").and_then(|v| v.as_str()) {
245                if let Ok(id) = id_str.parse::<u64>() {
246                    obj.insert("user_id".to_string(), id.into());
247                }
248            }
249        }
250        let params: DetectorGroupUserInsertParams =
251            serde_json::from_value(params).context(JsonSnafu)?;
252        let row: (i64,) = sqlx::query_as(
253            r#"INSERT INTO detector_group_users (user_id, group_id, role, status, effective_start_time, effective_end_time, invited_by, created_by, remark) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id"#,
254        )
255        .bind(params.user_id as i64)
256        .bind(params.group_id as i64)
257        .bind(params.role)
258        .bind(params.status)
259        .bind(parse_primitive_datetime(&params.effective_start_time)?)
260        .bind(parse_primitive_datetime(&params.effective_end_time)?)
261        .bind(params.invited_by as i64)
262        .bind(params.created_by as i64)
263        .bind(params.remark)
264        .fetch_one(pool)
265        .await
266        .context(SqlxSnafu)?;
267
268        Ok(row.0 as u64)
269    }
270
271    async fn get_by_id(&self, pool: &Pool<Postgres>, id: u64) -> Result<Option<Self::Output>> {
272        let result = sqlx::query_as::<_, DetectorGroupUserSchema>(
273            r#"SELECT * FROM detector_group_users WHERE id = $1 AND deleted_at IS NULL"#,
274        )
275        .bind(id as i64)
276        .fetch_optional(pool)
277        .await
278        .context(SqlxSnafu)?;
279
280        Ok(result.map(|schema| schema.into()))
281    }
282
283    async fn delete_by_id(&self, pool: &Pool<Postgres>, id: u64) -> Result<()> {
284        sqlx::query(
285            r#"UPDATE detector_group_users SET deleted_at = NOW(), modified = NOW() WHERE id = $1"#,
286        )
287        .bind(id as i64)
288        .execute(pool)
289        .await
290        .context(SqlxSnafu)?;
291
292        Ok(())
293    }
294
295    async fn update_by_id(
296        &self,
297        pool: &Pool<Postgres>,
298        id: u64,
299        params: serde_json::Value,
300    ) -> Result<()> {
301        let params: DetectorGroupUserUpdateParams =
302            serde_json::from_value(params).context(JsonSnafu)?;
303
304        let _ = sqlx::query(
305            r#"UPDATE detector_group_users SET role = COALESCE($1, role), status = COALESCE($2, status), effective_start_time = COALESCE($3, effective_start_time), effective_end_time = COALESCE($4, effective_end_time), invited_by = COALESCE($5, invited_by), remark = COALESCE($6, remark), modified = NOW() WHERE id = $7 AND deleted_at IS NULL"#,
306        )
307        .bind(params.role)
308        .bind(params.status)
309        .bind(params.effective_start_time.as_deref().map(parse_primitive_datetime).transpose()?)
310        .bind(params.effective_end_time.as_deref().map(parse_primitive_datetime).transpose()?)
311        .bind(params.invited_by.map(|v| v as i64))
312        .bind(params.remark)
313        .bind(id as i64)
314        .execute(pool)
315        .await
316        .context(SqlxSnafu)?;
317
318        Ok(())
319    }
320
321    async fn count(&self, pool: &Pool<Postgres>, params: &ModelListParams) -> Result<i64> {
322        let mut qb = QueryBuilder::new("SELECT COUNT(*) FROM detector_group_users");
323        self.push_conditions(&mut qb, params)?;
324        let count = qb
325            .build_query_scalar::<i64>()
326            .fetch_one(pool)
327            .await
328            .context(SqlxSnafu)?;
329        Ok(count)
330    }
331
332    async fn list(
333        &self,
334        pool: &Pool<Postgres>,
335        params: &ModelListParams,
336    ) -> Result<Vec<Self::Output>> {
337        let mut qb = QueryBuilder::new("SELECT * FROM detector_group_users");
338        self.push_conditions(&mut qb, params)?;
339        params.push_pagination(&mut qb);
340        let users = qb
341            .build_query_as::<DetectorGroupUserSchema>()
342            .fetch_all(pool)
343            .await
344            .context(SqlxSnafu)?;
345        Ok(users.into_iter().map(|s| s.into()).collect())
346    }
347}