1use super::{
16 Error, JsonSnafu, Model, ModelListParams, Schema, SchemaAllowEdit, SchemaOption,
17 SchemaOptionValue, SchemaType, SchemaView, SqlxSnafu, Status, format_datetime,
18 new_schema_options,
19};
20use serde::{Deserialize, Serialize};
21use snafu::ResultExt;
22use sqlx::FromRow;
23use sqlx::types::Json;
24use sqlx::{Pool, Postgres, QueryBuilder};
25use std::collections::HashMap;
26use time::PrimitiveDateTime;
27type Result<T> = std::result::Result<T, Error>;
28
29pub const ROLE_ADMIN: &str = "admin";
30pub const ROLE_SUPER_ADMIN: &str = "su";
31
32#[derive(FromRow)]
33struct UserSchema {
34 id: i64,
35 status: i16,
36 created: PrimitiveDateTime,
37 modified: PrimitiveDateTime,
38 account: String,
39 password: String,
40 nickname: Option<String>,
41 phone: Option<String>,
42 roles: Option<Json<Vec<String>>>,
43 groups: Option<Json<Vec<String>>>,
44 remark: Option<String>,
45 email: Option<String>,
46 avatar: Option<String>,
47 last_login_at: Option<PrimitiveDateTime>,
48}
49
50#[derive(Deserialize, Serialize)]
51pub struct User {
52 pub id: i64,
53 pub status: i16,
54 pub created: String,
55 pub modified: String,
56 pub account: String,
57 #[serde(skip_serializing)]
58 pub password: String,
59 pub nickname: Option<String>,
60 pub phone: Option<String>,
61 pub roles: Option<Vec<String>>,
62 pub groups: Option<Vec<String>>,
63 pub remark: Option<String>,
64 pub email: Option<String>,
65 pub avatar: Option<String>,
66 pub last_login_at: Option<String>,
67}
68
69impl From<UserSchema> for User {
70 fn from(user: UserSchema) -> Self {
71 Self {
72 id: user.id,
73 status: user.status,
74 created: format_datetime(user.created),
75 modified: format_datetime(user.modified),
76 account: user.account,
77 password: user.password,
78 nickname: user.nickname,
79 phone: user.phone,
80 roles: user.roles.map(|roles| roles.0),
81 groups: user.groups.map(|groups| groups.0),
82 remark: user.remark,
83 email: user.email,
84 avatar: user.avatar,
85 last_login_at: user.last_login_at.map(format_datetime),
86 }
87 }
88}
89
90#[derive(Debug, Clone, Deserialize, Default)]
91pub struct UserUpdateParams {
92 pub nickname: Option<String>,
93 pub phone: Option<String>,
94 pub email: Option<String>,
95 pub avatar: Option<String>,
96 pub roles: Option<Vec<String>>,
97 pub groups: Option<Vec<String>>,
98 pub status: Option<i16>,
99}
100
101pub struct UserModel {}
102
103impl Model for UserModel {
104 type Output = User;
105 fn new() -> Self {
106 Self {}
107 }
108 fn keyword(&self) -> String {
109 "account".to_string()
110 }
111 async fn schema_view(&self, _pool: &Pool<Postgres>) -> SchemaView {
112 SchemaView {
113 schemas: vec![
114 Schema::new_id(),
115 Schema {
116 name: "account".to_string(),
117 category: SchemaType::String,
118 read_only: true,
119 required: true,
120 identity: true,
121 ..Default::default()
122 },
123 Schema::new_status(),
124 Schema {
125 name: "nickname".to_string(),
126 category: SchemaType::String,
127 ..Default::default()
128 },
129 Schema {
130 name: "phone".to_string(),
131 category: SchemaType::String,
132 ..Default::default()
133 },
134 Schema {
135 name: "roles".to_string(),
136 category: SchemaType::Strings,
137 options: Some(new_schema_options(&[ROLE_ADMIN, ROLE_SUPER_ADMIN])),
138 ..Default::default()
139 },
140 Schema {
141 name: "groups".to_string(),
142 category: SchemaType::Strings,
143 options: Some(new_schema_options(&["it", "marketing"])),
144 ..Default::default()
145 },
146 Schema {
147 name: "last_login_at".to_string(),
148 category: SchemaType::Date,
149 read_only: true,
150 ..Default::default()
151 },
152 Schema::new_created(),
153 Schema::new_modified(),
154 ],
155 allow_edit: SchemaAllowEdit {
156 owner: true,
157 roles: vec![ROLE_SUPER_ADMIN.to_string()],
158 ..Default::default()
159 },
160 ..Default::default()
161 }
162 }
163 async fn get_by_id(&self, pool: &Pool<Postgres>, id: u64) -> Result<Option<Self::Output>> {
164 let result = sqlx::query_as::<_, UserSchema>(
165 r#"SELECT * FROM users WHERE id = $1 AND deleted_at IS NULL"#,
166 )
167 .bind(id as i64)
168 .fetch_optional(pool)
169 .await
170 .context(SqlxSnafu)?;
171
172 Ok(result.map(|user| user.into()))
173 }
174 async fn delete_by_id(&self, pool: &Pool<Postgres>, id: u64) -> Result<()> {
175 sqlx::query(
176 r#"UPDATE users SET deleted_at = CURRENT_TIMESTAMP WHERE id = $1 AND deleted_at IS NULL"#
177 )
178 .bind(id as i64)
179 .execute(pool)
180 .await
181 .context(SqlxSnafu)?;
182 Ok(())
183 }
184 async fn update_by_id(
185 &self,
186 pool: &Pool<Postgres>,
187 id: u64,
188 data: serde_json::Value,
189 ) -> Result<()> {
190 let params: UserUpdateParams = serde_json::from_value(data).context(JsonSnafu)?;
191 let _ = sqlx::query(
192 r#"
193 UPDATE users SET
194 email = COALESCE($1, email),
195 avatar = COALESCE($2, avatar),
196 roles = COALESCE($3, roles),
197 groups = COALESCE($4, groups),
198 status = COALESCE($5, status),
199 nickname = COALESCE($6, nickname),
200 phone = COALESCE($7, phone)
201 WHERE id = $8 AND deleted_at IS NULL
202 "#,
203 )
204 .bind(params.email.as_deref())
205 .bind(params.avatar.as_deref())
206 .bind(params.roles.map(Json))
207 .bind(params.groups.map(Json))
208 .bind(params.status)
209 .bind(params.nickname.as_deref())
210 .bind(params.phone.as_deref())
211 .bind(id as i64)
212 .execute(pool)
213 .await
214 .context(SqlxSnafu)?;
215
216 Ok(())
217 }
218 fn push_filter_conditions<'args>(
219 &self,
220 qb: &mut QueryBuilder<'args, Postgres>,
221 filters: &HashMap<String, String>,
222 ) -> Result<()> {
223 if let Some(status) = filters.get("status").and_then(|s| s.parse::<i16>().ok()) {
224 qb.push(" AND status = ");
225 qb.push_bind(status);
226 }
227 if let Some(role) = filters.get("role") {
228 qb.push(" AND roles @> ");
229 qb.push_bind(Json(vec![role.clone()]));
230 qb.push("::jsonb");
231 }
232 if let Some(group) = filters.get("group") {
233 qb.push(" AND groups @> ");
234 qb.push_bind(Json(vec![group.clone()]));
235 qb.push("::jsonb");
236 }
237 Ok(())
238 }
239
240 async fn count(&self, pool: &Pool<Postgres>, params: &ModelListParams) -> Result<i64> {
241 let mut qb = QueryBuilder::new("SELECT COUNT(*) FROM users");
242 self.push_conditions(&mut qb, params)?;
243 let count = qb
244 .build_query_scalar::<i64>()
245 .fetch_one(pool)
246 .await
247 .context(SqlxSnafu)?;
248 Ok(count)
249 }
250
251 async fn list(
252 &self,
253 pool: &Pool<Postgres>,
254 params: &ModelListParams,
255 ) -> Result<Vec<Self::Output>> {
256 let mut qb = QueryBuilder::new("SELECT * FROM users");
257 self.push_conditions(&mut qb, params)?;
258 params.push_pagination(&mut qb);
259 let result = qb
260 .build_query_as::<UserSchema>()
261 .fetch_all(pool)
262 .await
263 .context(SqlxSnafu)?;
264 Ok(result.into_iter().map(|u| u.into()).collect())
265 }
266 async fn search_options(
267 &self,
268 pool: &Pool<Postgres>,
269 keyword: Option<String>,
270 ) -> Result<Vec<SchemaOption>> {
271 let params = ModelListParams {
272 keyword,
273 limit: 20,
274 page: 1,
275 ..Default::default()
276 };
277 let users = self.list(pool, ¶ms).await?;
278 Ok(users
279 .into_iter()
280 .map(|u| SchemaOption {
281 label: u.account,
282 value: SchemaOptionValue::String(u.id.to_string()),
283 })
284 .collect())
285 }
286}
287
288impl UserModel {
289 pub async fn register(
290 &self,
291 pool: &Pool<Postgres>,
292 account: &str,
293 password: &str,
294 ) -> Result<u64> {
295 let row: (i64,) = sqlx::query_as(
299 r#"
300 INSERT INTO users (
301 status, account, password
302 ) VALUES (
303 $1, $2, $3
304 ) RETURNING id
305 "#,
306 )
307 .bind(Status::Enabled as i16)
308 .bind(account)
309 .bind(password)
310 .fetch_one(pool)
311 .await
312 .context(SqlxSnafu)?;
313
314 Ok(row.0 as u64)
315 }
316
317 pub async fn get_by_account(
318 &self,
319 pool: &Pool<Postgres>,
320 account: &str,
321 ) -> Result<Option<User>> {
322 let result = sqlx::query_as::<_, UserSchema>(
323 r#"SELECT * FROM users WHERE account = $1 AND deleted_at IS NULL"#,
324 )
325 .bind(account)
326 .fetch_optional(pool)
327 .await
328 .context(SqlxSnafu)?;
329
330 Ok(result.map(|user| user.into()))
331 }
332
333 pub async fn update_by_account(
334 &self,
335 pool: &Pool<Postgres>,
336 account: &str,
337 params: UserUpdateParams,
338 ) -> Result<()> {
339 let _ = sqlx::query(
340 r#"
341 UPDATE users SET
342 email = COALESCE($1, email),
343 avatar = COALESCE($2, avatar),
344 nickname = COALESCE($3, nickname),
345 phone = COALESCE($4, phone)
346 WHERE account = $5 AND deleted_at IS NULL
347 "#,
348 )
349 .bind(params.email.as_deref())
350 .bind(params.avatar.as_deref())
351 .bind(params.nickname.as_deref())
352 .bind(params.phone.as_deref())
353 .bind(account)
354 .execute(pool)
355 .await
356 .context(SqlxSnafu)?;
357 Ok(())
358 }
359
360 pub async fn update_last_login_at(&self, pool: &Pool<Postgres>, account: &str) -> Result<()> {
362 sqlx::query(
363 r#"UPDATE users SET last_login_at = CURRENT_TIMESTAMP WHERE account = $1 AND deleted_at IS NULL"#,
364 )
365 .bind(account)
366 .execute(pool)
367 .await
368 .context(SqlxSnafu)?;
369 Ok(())
370 }
371}