1use super::{
16 Error, JsonSnafu, ModelListParams, Schema, SchemaAllowCreate, SchemaAllowEdit, SchemaType,
17 SchemaView, SqlxSnafu, format_datetime,
18};
19use serde::{Deserialize, Serialize};
20use snafu::ResultExt;
21use sqlx::FromRow;
22use sqlx::{Pool, Postgres, QueryBuilder};
23use std::collections::HashMap;
24use tibba_model::Model;
25use time::PrimitiveDateTime;
26
27type Result<T> = std::result::Result<T, Error>;
28
29#[derive(FromRow)]
30struct TokenKeySchema {
31 id: i64,
32 user_id: i64,
33 token: String,
34 name: String,
35 status: i16,
36 expired_at: Option<PrimitiveDateTime>,
37 created_by: i64,
38 created: PrimitiveDateTime,
39 modified: PrimitiveDateTime,
40}
41
42#[derive(Debug, Clone, Deserialize, Serialize)]
43pub struct TokenKey {
44 pub id: i64,
45 pub user_id: i64,
46 pub token: String,
47 pub name: String,
48 pub status: i16,
49 pub expired_at: Option<String>,
50 pub created_by: i64,
51 pub created: String,
52 pub modified: String,
53}
54
55impl From<TokenKeySchema> for TokenKey {
56 fn from(s: TokenKeySchema) -> Self {
57 Self {
58 id: s.id,
59 user_id: s.user_id,
60 token: s.token,
61 name: s.name,
62 status: s.status,
63 expired_at: s.expired_at.map(format_datetime),
64 created_by: s.created_by,
65 created: format_datetime(s.created),
66 modified: format_datetime(s.modified),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Deserialize)]
72pub struct TokenKeyInsertParams {
73 pub user_id: i64,
74 pub name: String,
75 pub created_by: Option<i64>,
76}
77
78#[derive(Debug, Clone, Deserialize, Default)]
79pub struct TokenKeyUpdateParams {
80 pub name: Option<String>,
81 pub status: Option<i16>,
82 pub expired_at: Option<String>,
83}
84
85#[derive(Default)]
86pub struct TokenKeyModel {}
87
88impl TokenKeyModel {
89 pub async fn get_user_id_by_token(
92 &self,
93 pool: &Pool<Postgres>,
94 token: &str,
95 ) -> Result<Option<i64>> {
96 let result = sqlx::query_as::<_, (i64,)>(
97 r#"SELECT user_id FROM token_keys
98 WHERE token = $1
99 AND status = 1
100 AND deleted_at IS NULL
101 AND (expired_at IS NULL OR expired_at > NOW())"#,
102 )
103 .bind(token)
104 .fetch_optional(pool)
105 .await
106 .context(SqlxSnafu)?;
107 Ok(result.map(|r| r.0))
108 }
109}
110
111impl Model for TokenKeyModel {
112 type Output = TokenKey;
113 fn new() -> Self {
114 Self::default()
115 }
116
117 async fn schema_view(&self, _pool: &Pool<Postgres>) -> SchemaView {
118 SchemaView {
119 schemas: vec![
120 Schema::new_id(),
121 Schema::new_user_search("user_id"),
122 Schema {
123 name: "token".to_string(),
124 category: SchemaType::String,
125 read_only: true,
126 auto_create: true,
127 popover: true,
128 ..Default::default()
129 },
130 Schema::new_name(),
131 Schema::new_status(),
132 Schema {
133 name: "expired_at".to_string(),
134 category: SchemaType::Date,
135 ..Default::default()
136 },
137 Schema {
138 name: "created_by".to_string(),
139 category: SchemaType::Number,
140 read_only: true,
141 hidden: true,
142 auto_create: true,
143 ..Default::default()
144 },
145 Schema::new_created(),
146 Schema::new_modified(),
147 ],
148 allow_edit: SchemaAllowEdit {
149 roles: vec!["su".to_string(), "admin".to_string()],
150 ..Default::default()
151 },
152 allow_create: SchemaAllowCreate {
153 roles: vec!["su".to_string(), "admin".to_string()],
154 ..Default::default()
155 },
156 }
157 }
158
159 async fn insert(&self, pool: &Pool<Postgres>, mut data: serde_json::Value) -> Result<u64> {
160 if let Some(obj) = data.as_object_mut() {
162 if let Some(id_str) = obj.get("user_id").and_then(|v| v.as_str()) {
163 if let Ok(id) = id_str.parse::<i64>() {
164 obj.insert("user_id".to_string(), id.into());
165 }
166 }
167 }
168 let p: TokenKeyInsertParams = serde_json::from_value(data).context(JsonSnafu)?;
169 let token = uuid::Uuid::new_v4().to_string();
170 let row: (i64,) = sqlx::query_as(
171 r#"INSERT INTO token_keys (user_id, name, token, created_by)
172 VALUES ($1, $2, $3, $4) RETURNING id"#,
173 )
174 .bind(p.user_id)
175 .bind(&p.name)
176 .bind(&token)
177 .bind(p.created_by.unwrap_or(0))
178 .fetch_one(pool)
179 .await
180 .context(SqlxSnafu)?;
181 Ok(row.0 as u64)
182 }
183
184 async fn get_by_id(&self, pool: &Pool<Postgres>, id: u64) -> Result<Option<Self::Output>> {
185 let result = sqlx::query_as::<_, TokenKeySchema>(
186 r#"SELECT * FROM token_keys WHERE id = $1 AND deleted_at IS NULL"#,
187 )
188 .bind(id as i64)
189 .fetch_optional(pool)
190 .await
191 .context(SqlxSnafu)?;
192 Ok(result.map(Into::into))
193 }
194
195 async fn update_by_id(
196 &self,
197 pool: &Pool<Postgres>,
198 id: u64,
199 data: serde_json::Value,
200 ) -> Result<()> {
201 let p: TokenKeyUpdateParams = serde_json::from_value(data).context(JsonSnafu)?;
202 let mut qb: QueryBuilder<Postgres> =
203 QueryBuilder::new("UPDATE token_keys SET modified = NOW()");
204 if let Some(name) = p.name {
205 qb.push(", name = ").push_bind(name);
206 }
207 if let Some(status) = p.status {
208 qb.push(", status = ").push_bind(status);
209 }
210 if let Some(expired_at) = p.expired_at {
211 if expired_at.is_empty() {
212 qb.push(", expired_at = NULL");
213 } else {
214 let dt = tibba_model::parse_primitive_datetime(&expired_at).map_err(|_| {
215 Error::NotSupported {
216 name: "invalid expired_at".to_string(),
217 }
218 })?;
219 qb.push(", expired_at = ").push_bind(dt);
220 }
221 }
222 qb.push(" WHERE id = ").push_bind(id as i64);
223 qb.push(" AND deleted_at IS NULL");
224 qb.build().execute(pool).await.context(SqlxSnafu)?;
225 Ok(())
226 }
227
228 async fn delete_by_id(&self, pool: &Pool<Postgres>, id: u64) -> Result<()> {
229 sqlx::query(
230 r#"UPDATE token_keys SET deleted_at = NOW(), modified = NOW() WHERE id = $1 AND deleted_at IS NULL"#,
231 )
232 .bind(id as i64)
233 .execute(pool)
234 .await
235 .context(SqlxSnafu)?;
236 Ok(())
237 }
238
239 async fn count(&self, pool: &Pool<Postgres>, params: &ModelListParams) -> Result<i64> {
240 let mut qb: QueryBuilder<Postgres> = QueryBuilder::new("SELECT COUNT(*) FROM token_keys");
241 self.push_conditions(&mut qb, params)?;
242 let row: (i64,) = qb
243 .build_query_as()
244 .fetch_one(pool)
245 .await
246 .context(SqlxSnafu)?;
247 Ok(row.0)
248 }
249
250 async fn list(
251 &self,
252 pool: &Pool<Postgres>,
253 params: &ModelListParams,
254 ) -> Result<Vec<Self::Output>> {
255 let mut qb: QueryBuilder<Postgres> = QueryBuilder::new("SELECT * FROM token_keys");
256 self.push_conditions(&mut qb, params)?;
257 params.push_pagination(&mut qb);
258 let rows = qb
259 .build_query_as::<TokenKeySchema>()
260 .fetch_all(pool)
261 .await
262 .context(SqlxSnafu)?;
263 Ok(rows.into_iter().map(Into::into).collect())
264 }
265
266 fn push_filter_conditions<'args>(
267 &self,
268 qb: &mut QueryBuilder<'args, Postgres>,
269 filters: &HashMap<String, String>,
270 ) -> Result<()> {
271 if let Some(user_id) = filters.get("user_id") {
272 if let Ok(v) = user_id.parse::<i64>() {
273 qb.push(" AND user_id = ").push_bind(v);
274 }
275 }
276 Ok(())
277 }
278}