1use super::{
16 Error, JsonSnafu, ModelListParams, Schema, SchemaAllowCreate, SchemaAllowEdit, SchemaOption,
17 SchemaOptionValue, SchemaType, 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 TokenRechargeSchema {
31 id: i64,
32 user_id: i64,
33 amount: i64,
34 source: i16,
35 order_id: String,
36 expired_at: Option<PrimitiveDateTime>,
37 remark: String,
38 created_by: i64,
39 created: PrimitiveDateTime,
40 modified: PrimitiveDateTime,
41}
42
43#[derive(Debug, Clone, Deserialize, Serialize)]
44pub struct TokenRecharge {
45 pub id: i64,
46 pub user_id: i64,
47 pub amount: i64,
48 pub source: i16,
49 pub order_id: String,
50 pub expired_at: Option<String>,
51 pub remark: String,
52 pub created_by: i64,
53 pub created: String,
54 pub modified: String,
55}
56
57impl From<TokenRechargeSchema> for TokenRecharge {
58 fn from(s: TokenRechargeSchema) -> Self {
59 Self {
60 id: s.id,
61 user_id: s.user_id,
62 amount: s.amount,
63 source: s.source,
64 order_id: s.order_id,
65 expired_at: s.expired_at.map(format_datetime),
66 remark: s.remark,
67 created_by: s.created_by,
68 created: format_datetime(s.created),
69 modified: format_datetime(s.modified),
70 }
71 }
72}
73
74#[derive(Debug, Clone, Deserialize, Default)]
75pub struct TokenRechargeInsertParams {
76 pub user_id: i64,
77 pub amount: i64,
78 pub source: i16,
79 pub order_id: Option<String>,
80 pub expired_at: Option<String>,
81 pub remark: Option<String>,
82 pub created_by: Option<i64>,
83}
84
85#[derive(Default)]
86pub struct TokenRechargeModel {}
87
88impl TokenRechargeModel {
89 pub async fn list_by_user(
91 &self,
92 pool: &Pool<Postgres>,
93 user_id: i64,
94 page: u64,
95 limit: u64,
96 ) -> Result<Vec<TokenRecharge>> {
97 let limit = limit.min(200);
98 let offset = (page.max(1) - 1) * limit;
99 let rows = sqlx::query_as::<_, TokenRechargeSchema>(
100 r#"SELECT * FROM token_recharges WHERE user_id = $1 AND deleted_at IS NULL ORDER BY id DESC LIMIT $2 OFFSET $3"#,
101 )
102 .bind(user_id)
103 .bind(limit as i64)
104 .bind(offset as i64)
105 .fetch_all(pool)
106 .await
107 .context(SqlxSnafu)?;
108 Ok(rows.into_iter().map(Into::into).collect())
109 }
110
111 pub async fn get_by_order_id(
113 &self,
114 pool: &Pool<Postgres>,
115 order_id: &str,
116 ) -> Result<Option<TokenRecharge>> {
117 let result = sqlx::query_as::<_, TokenRechargeSchema>(
118 r#"SELECT * FROM token_recharges WHERE order_id = $1 AND deleted_at IS NULL LIMIT 1"#,
119 )
120 .bind(order_id)
121 .fetch_optional(pool)
122 .await
123 .context(SqlxSnafu)?;
124 Ok(result.map(Into::into))
125 }
126}
127
128impl Model for TokenRechargeModel {
129 type Output = TokenRecharge;
130 fn new() -> Self {
131 Self::default()
132 }
133
134 async fn schema_view(&self, _pool: &Pool<Postgres>) -> SchemaView {
135 let source_options = vec![
136 SchemaOption {
137 label: "购买".to_string(),
138 value: SchemaOptionValue::Integer(1),
139 },
140 SchemaOption {
141 label: "赠送".to_string(),
142 value: SchemaOptionValue::Integer(2),
143 },
144 SchemaOption {
145 label: "退款".to_string(),
146 value: SchemaOptionValue::Integer(3),
147 },
148 SchemaOption {
149 label: "管理员调整".to_string(),
150 value: SchemaOptionValue::Integer(4),
151 },
152 ];
153 SchemaView {
154 schemas: vec![
155 Schema::new_id(),
156 Schema {
157 filterable: true,
158 ..Schema::new_user_search("user_id")
159 },
160 Schema {
161 name: "amount".to_string(),
162 category: SchemaType::Number,
163 required: true,
164 ..Default::default()
165 },
166 Schema {
167 name: "source".to_string(),
168 category: SchemaType::Number,
169 required: true,
170 options: Some(source_options),
171 filterable: true,
172 ..Default::default()
173 },
174 Schema {
175 name: "order_id".to_string(),
176 category: SchemaType::String,
177 ..Default::default()
178 },
179 Schema {
180 name: "expired_at".to_string(),
181 category: SchemaType::Date,
182 ..Default::default()
183 },
184 Schema::new_remark(),
185 Schema {
186 name: "created_by".to_string(),
187 category: SchemaType::Number,
188 read_only: true,
189 ..Default::default()
190 },
191 Schema::new_created(),
192 Schema::new_filterable_modified(),
193 ],
194 allow_edit: SchemaAllowEdit {
195 roles: vec!["su".to_string(), "admin".to_string()],
196 ..Default::default()
197 },
198 allow_create: SchemaAllowCreate {
199 roles: vec!["su".to_string(), "admin".to_string()],
200 ..Default::default()
201 },
202 }
203 }
204
205 async fn insert(&self, pool: &Pool<Postgres>, mut data: serde_json::Value) -> Result<u64> {
206 if let Some(obj) = data.as_object_mut() {
208 if let Some(id_str) = obj.get("user_id").and_then(|v| v.as_str()) {
209 if let Ok(id) = id_str.parse::<i64>() {
210 obj.insert("user_id".to_string(), id.into());
211 }
212 }
213 }
214 let p: TokenRechargeInsertParams = serde_json::from_value(data).context(JsonSnafu)?;
215 let row: (i64,) = sqlx::query_as(
216 r#"INSERT INTO token_recharges (user_id, amount, source, order_id, remark, created_by)
217 VALUES ($1, $2, $3, $4, $5, $6) RETURNING id"#,
218 )
219 .bind(p.user_id)
220 .bind(p.amount)
221 .bind(p.source)
222 .bind(p.order_id.unwrap_or_default())
223 .bind(p.remark.unwrap_or_default())
224 .bind(p.created_by.unwrap_or(0))
225 .fetch_one(pool)
226 .await
227 .context(SqlxSnafu)?;
228 Ok(row.0 as u64)
229 }
230
231 async fn get_by_id(&self, pool: &Pool<Postgres>, id: u64) -> Result<Option<Self::Output>> {
232 let result = sqlx::query_as::<_, TokenRechargeSchema>(
233 r#"SELECT * FROM token_recharges WHERE id = $1 AND deleted_at IS NULL"#,
234 )
235 .bind(id as i64)
236 .fetch_optional(pool)
237 .await
238 .context(SqlxSnafu)?;
239 Ok(result.map(Into::into))
240 }
241
242 async fn delete_by_id(&self, pool: &Pool<Postgres>, id: u64) -> Result<()> {
243 sqlx::query(
244 r#"UPDATE token_recharges SET deleted_at = NOW(), modified = NOW() WHERE id = $1 AND deleted_at IS NULL"#,
245 )
246 .bind(id as i64)
247 .execute(pool)
248 .await
249 .context(SqlxSnafu)?;
250 Ok(())
251 }
252
253 async fn count(&self, pool: &Pool<Postgres>, params: &ModelListParams) -> Result<i64> {
254 let mut qb: QueryBuilder<Postgres> =
255 QueryBuilder::new("SELECT COUNT(*) FROM token_recharges");
256 self.push_conditions(&mut qb, params)?;
257 let row: (i64,) = qb
258 .build_query_as()
259 .fetch_one(pool)
260 .await
261 .context(SqlxSnafu)?;
262 Ok(row.0)
263 }
264
265 async fn list(
266 &self,
267 pool: &Pool<Postgres>,
268 params: &ModelListParams,
269 ) -> Result<Vec<Self::Output>> {
270 let mut qb: QueryBuilder<Postgres> = QueryBuilder::new("SELECT * FROM token_recharges");
271 self.push_conditions(&mut qb, params)?;
272 params.push_pagination(&mut qb);
273 let rows = qb
274 .build_query_as::<TokenRechargeSchema>()
275 .fetch_all(pool)
276 .await
277 .context(SqlxSnafu)?;
278 Ok(rows.into_iter().map(Into::into).collect())
279 }
280
281 fn push_filter_conditions<'args>(
282 &self,
283 qb: &mut QueryBuilder<'args, Postgres>,
284 filters: &HashMap<String, String>,
285 ) -> Result<()> {
286 if let Some(user_id) = filters.get("user_id") {
287 if let Ok(v) = user_id.parse::<i64>() {
288 qb.push(" AND user_id = ").push_bind(v);
289 }
290 }
291 if let Some(source) = filters.get("source") {
292 if let Ok(v) = source.parse::<i16>() {
293 qb.push(" AND source = ").push_bind(v);
294 }
295 }
296 Ok(())
297 }
298}