tdm_server_rust/repository/
questionnaire_repo.rs1use crate::db::DbConn;
6use crate::entity::questionnaire::Questionnaire;
7use crate::sea_entity::{mangaepisodedetail, mangaepisodetb, memberpost, questionnaire};
8use chrono::{DateTime, Utc};
9use sea_orm::{
10 ActiveModelTrait, ColumnTrait, ConnectionTrait, DatabaseConnection, DbBackend, EntityTrait,
11 JoinType, QueryFilter, QueryOrder, QuerySelect, RelationTrait, Set, Statement, Value,
12};
13use sqlx::postgres::PgPool;
14use std::collections::HashMap;
15
16pub struct QuestionnaireRepository {
18 db: DbConn,
20}
21
22impl QuestionnaireRepository {
23 pub fn new(pool: PgPool) -> Self {
25 Self {
26 db: crate::db::from_sqlx_pool(pool),
27 }
28 }
29
30 pub fn from_db(db: DatabaseConnection) -> Self {
32 Self { db }
33 }
34
35 #[tracing::instrument(skip_all, level = "debug")]
37 pub async fn get_by_member_id(
38 &self,
39 member_id: i32,
40 ) -> crate::error::ApiResult<Option<Questionnaire>> {
41 let row = questionnaire::Entity::find()
42 .filter(questionnaire::Column::MemberId.eq(member_id as i64))
43 .one(&self.db)
44 .await?;
45 Ok(row.map(model_to_questionnaire))
46 }
47
48 #[tracing::instrument(skip_all, level = "debug")]
50 pub async fn list_all(
51 &self,
52 willingness: Option<i16>,
53 length: Option<&str>,
54 frequency: Option<i16>,
55 member_id: Option<i32>,
56 ) -> crate::error::ApiResult<Vec<Questionnaire>> {
57 let mut q = questionnaire::Entity::find();
58 if let Some(v) = willingness {
59 q = q.filter(questionnaire::Column::Willingness.eq(v));
60 }
61 if let Some(v) = length {
62 q = q.filter(questionnaire::Column::AcceptableLengths.eq(v));
63 }
64 if let Some(v) = frequency {
65 q = q.filter(questionnaire::Column::UpdateFrequency.eq(v));
66 }
67 if let Some(v) = member_id {
68 q = q.filter(questionnaire::Column::MemberId.eq(v as i64));
69 }
70 let rows = q.all(&self.db).await?;
71 Ok(rows.into_iter().map(model_to_questionnaire).collect())
72 }
73
74 #[tracing::instrument(skip_all, level = "debug")]
76 pub async fn list_by_post(
77 &self,
78 post_id: i32,
79 willingness: Option<i16>,
80 length: Option<&str>,
81 frequency: Option<i16>,
82 member_id: Option<i32>,
83 ) -> crate::error::ApiResult<Vec<Questionnaire>> {
84 let member_ids: Vec<i64> = memberpost::Entity::find()
85 .filter(memberpost::Column::PostId.eq(post_id))
86 .all(&self.db)
87 .await?
88 .into_iter()
89 .map(|m| m.member_id as i64)
90 .collect();
91 if member_ids.is_empty() {
92 return Ok(vec![]);
93 }
94 let mut q =
95 questionnaire::Entity::find().filter(questionnaire::Column::MemberId.is_in(member_ids));
96 if let Some(v) = willingness {
97 q = q.filter(questionnaire::Column::Willingness.eq(v));
98 }
99 if let Some(v) = length {
100 q = q.filter(questionnaire::Column::AcceptableLengths.eq(v));
101 }
102 if let Some(v) = frequency {
103 q = q.filter(questionnaire::Column::UpdateFrequency.eq(v));
104 }
105 if let Some(v) = member_id {
106 q = q.filter(questionnaire::Column::MemberId.eq(v as i64));
107 }
108 let rows = q.all(&self.db).await?;
109 Ok(rows.into_iter().map(model_to_questionnaire).collect())
110 }
111
112 #[tracing::instrument(skip_all, level = "debug")]
114 pub async fn insert(&self, q: &Questionnaire) -> crate::error::ApiResult<i64> {
115 let now = chrono::Utc::now().naive_utc();
116 let model = questionnaire::ActiveModel {
117 member_id: Set(q.member_id.unwrap_or(0) as i64),
118 willingness: Set(q.willingness.unwrap_or(0)),
119 acceptable_lengths: Set(q.acceptable_lengths.clone()),
120 update_frequency: Set(q.update_frequency),
121 preferred_genres: Set(q.preferred_genres.clone()),
122 avoid_topics: Set(q.avoid_topics.clone()),
123 note: Set(q.note.clone()),
124 remark: Set(q.remark.clone()),
125 created_at: Set(now),
126 updated_at: Set(now),
127 ..Default::default()
128 };
129 let inserted = model.insert(&self.db).await?;
130 Ok(inserted.id)
131 }
132
133 #[tracing::instrument(skip_all, level = "debug")]
135 pub async fn update(&self, q: &Questionnaire) -> crate::error::ApiResult<()> {
136 let id =
137 q.id.ok_or_else(|| crate::error::AppError::business("问卷 ID 不能为空"))?;
138 let model = questionnaire::ActiveModel {
139 id: Set(id),
140 willingness: Set(q.willingness.unwrap_or(0)),
141 acceptable_lengths: Set(q.acceptable_lengths.clone()),
142 update_frequency: Set(q.update_frequency),
143 preferred_genres: Set(q.preferred_genres.clone()),
144 avoid_topics: Set(q.avoid_topics.clone()),
145 note: Set(q.note.clone()),
146 remark: Set(q.remark.clone()),
147 updated_at: Set(chrono::Utc::now().naive_utc()),
148 ..Default::default()
149 };
150 model.update(&self.db).await?;
151 Ok(())
152 }
153
154 #[tracing::instrument(skip_all, level = "debug")]
156 pub async fn select_last_assigned_time(
157 &self,
158 member_id: i32,
159 ) -> crate::error::ApiResult<Option<DateTime<Utc>>> {
160 let row = mangaepisodedetail::Entity::find()
161 .join(
162 JoinType::InnerJoin,
163 mangaepisodedetail::Relation::Mangaepisodetb.def(),
164 )
165 .filter(mangaepisodetb::Column::TranslatorId.eq(member_id))
166 .filter(mangaepisodedetail::Column::TranslatorSetupTime.is_not_null())
167 .order_by_desc(mangaepisodedetail::Column::TranslatorSetupTime)
168 .one(&self.db)
169 .await?;
170 Ok(row.and_then(|r| r.translator_setup_time.map(|t| t.and_utc())))
171 }
172
173 #[tracing::instrument(skip_all, level = "debug")]
175 pub async fn select_last_assigned_times(
176 &self,
177 member_ids: &[i32],
178 ) -> crate::error::ApiResult<HashMap<i32, DateTime<Utc>>> {
179 if member_ids.is_empty() {
180 return Ok(HashMap::new());
181 }
182 let mut ids = member_ids.to_vec();
183 ids.sort_unstable();
184 ids.dedup();
185 let placeholders = (1..=ids.len())
186 .map(|i| format!("${i}"))
187 .collect::<Vec<_>>()
188 .join(", ");
189 let sql = format!(
190 r#"SELECT e."translatorId" AS member_id,
191 MAX(d."translatorSetupTime") AS last_assigned_time
192 FROM mangaepisodedetail d
193 INNER JOIN mangaepisodetb e ON e."Id" = d."episodeId"
194 WHERE e."translatorId" IN ({placeholders})
195 AND d."translatorSetupTime" IS NOT NULL
196 GROUP BY e."translatorId""#
197 );
198 let values: Vec<Value> = ids.into_iter().map(Value::from).collect();
199 let rows = self
200 .db
201 .query_all(Statement::from_sql_and_values(
202 DbBackend::Postgres,
203 &sql,
204 values,
205 ))
206 .await?;
207 let mut out = HashMap::new();
208 for row in rows {
209 let member_id: i32 = row.try_get("", "member_id")?;
210 let assigned: Option<chrono::NaiveDateTime> = row.try_get("", "last_assigned_time")?;
211 if let Some(assigned) = assigned {
212 out.insert(member_id, assigned.and_utc());
213 }
214 }
215 Ok(out)
216 }
217}
218
219fn model_to_questionnaire(m: questionnaire::Model) -> Questionnaire {
221 Questionnaire {
222 id: Some(m.id),
223 member_id: Some(m.member_id as i32),
224 willingness: Some(m.willingness),
225 acceptable_lengths: m.acceptable_lengths,
226 update_frequency: m.update_frequency,
227 preferred_genres: m.preferred_genres,
228 avoid_topics: m.avoid_topics,
229 note: m.note,
230 remark: m.remark,
231 last_assigned_time: m.last_assigned_time.map(|t| t.and_utc()),
232 created_at: Some(m.created_at.and_utc()),
233 updated_at: Some(m.updated_at.and_utc()),
234 }
235}