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 = NOW(), modified = NOW() 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 modified = NOW()
202 WHERE id = $8 AND deleted_at IS NULL
203 "#,
204 )
205 .bind(params.email.as_deref())
206 .bind(params.avatar.as_deref())
207 .bind(params.roles.map(Json))
208 .bind(params.groups.map(Json))
209 .bind(params.status)
210 .bind(params.nickname.as_deref())
211 .bind(params.phone.as_deref())
212 .bind(id as i64)
213 .execute(pool)
214 .await
215 .context(SqlxSnafu)?;
216
217 Ok(())
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(role) = filters.get("role") {
229 qb.push(" AND roles @> ");
230 qb.push_bind(Json(vec![role.clone()]));
231 qb.push("::jsonb");
232 }
233 if let Some(group) = filters.get("group") {
234 qb.push(" AND groups @> ");
235 qb.push_bind(Json(vec![group.clone()]));
236 qb.push("::jsonb");
237 }
238 Ok(())
239 }
240
241 async fn count(&self, pool: &Pool<Postgres>, params: &ModelListParams) -> Result<i64> {
242 let mut qb = QueryBuilder::new("SELECT COUNT(*) FROM users");
243 self.push_conditions(&mut qb, params)?;
244 let count = qb
245 .build_query_scalar::<i64>()
246 .fetch_one(pool)
247 .await
248 .context(SqlxSnafu)?;
249 Ok(count)
250 }
251
252 async fn list(
253 &self,
254 pool: &Pool<Postgres>,
255 params: &ModelListParams,
256 ) -> Result<Vec<Self::Output>> {
257 let mut qb = QueryBuilder::new("SELECT * FROM users");
258 self.push_conditions(&mut qb, params)?;
259 params.push_pagination(&mut qb);
260 let result = qb
261 .build_query_as::<UserSchema>()
262 .fetch_all(pool)
263 .await
264 .context(SqlxSnafu)?;
265 Ok(result.into_iter().map(|u| u.into()).collect())
266 }
267 async fn search_options(
268 &self,
269 pool: &Pool<Postgres>,
270 keyword: Option<String>,
271 ) -> Result<Vec<SchemaOption>> {
272 let params = ModelListParams {
273 keyword,
274 limit: 20,
275 page: 1,
276 ..Default::default()
277 };
278 let users = self.list(pool, ¶ms).await?;
279 Ok(users
280 .into_iter()
281 .map(|u| SchemaOption {
282 label: u.account,
283 value: SchemaOptionValue::String(u.id.to_string()),
284 })
285 .collect())
286 }
287}
288
289impl UserModel {
290 pub async fn register(
291 &self,
292 pool: &Pool<Postgres>,
293 account: &str,
294 password: &str,
295 ) -> Result<u64> {
296 let row: (i64,) = sqlx::query_as(
300 r#"
301 INSERT INTO users (
302 status, account, password
303 ) VALUES (
304 $1, $2, $3
305 ) RETURNING id
306 "#,
307 )
308 .bind(Status::Enabled as i16)
309 .bind(account)
310 .bind(password)
311 .fetch_one(pool)
312 .await
313 .context(SqlxSnafu)?;
314
315 Ok(row.0 as u64)
316 }
317
318 pub async fn get_by_account(
319 &self,
320 pool: &Pool<Postgres>,
321 account: &str,
322 ) -> Result<Option<User>> {
323 let result = sqlx::query_as::<_, UserSchema>(
324 r#"SELECT * FROM users WHERE account = $1 AND deleted_at IS NULL"#,
325 )
326 .bind(account)
327 .fetch_optional(pool)
328 .await
329 .context(SqlxSnafu)?;
330
331 Ok(result.map(|user| user.into()))
332 }
333
334 pub async fn update_by_account(
335 &self,
336 pool: &Pool<Postgres>,
337 account: &str,
338 params: UserUpdateParams,
339 ) -> Result<()> {
340 let _ = sqlx::query(
341 r#"
342 UPDATE users SET
343 email = COALESCE($1, email),
344 avatar = COALESCE($2, avatar),
345 nickname = COALESCE($3, nickname),
346 phone = COALESCE($4, phone),
347 modified = NOW()
348 WHERE account = $5 AND deleted_at IS NULL
349 "#,
350 )
351 .bind(params.email.as_deref())
352 .bind(params.avatar.as_deref())
353 .bind(params.nickname.as_deref())
354 .bind(params.phone.as_deref())
355 .bind(account)
356 .execute(pool)
357 .await
358 .context(SqlxSnafu)?;
359 Ok(())
360 }
361
362 pub async fn update_last_login_at(&self, pool: &Pool<Postgres>, account: &str) -> Result<()> {
364 sqlx::query(
365 r#"UPDATE users SET last_login_at = CURRENT_TIMESTAMP WHERE account = $1 AND deleted_at IS NULL"#,
366 )
367 .bind(account)
368 .execute(pool)
369 .await
370 .context(SqlxSnafu)?;
371 Ok(())
372 }
373}