Skip to content

Commit c86ec58

Browse files
committed
refactor(panel): add pagination for tickets tab
1 parent f4ee185 commit c86ec58

6 files changed

Lines changed: 347 additions & 123 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ config.toml
33
config.toml.backup
44
node_modules
55
.env
6-
db/db.sqlite
6+
db/
77
.idea
88
.vscode/
99
package-lock.json

rustmail/src/api/handler/bot/tickets.rs

Lines changed: 171 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ pub struct CompleteThread {
4343
#[derive(Debug, Deserialize)]
4444
pub struct TicketQuery {
4545
pub id: Option<String>,
46+
pub page: Option<i64>,
47+
pub page_size: Option<i64>,
48+
pub status: Option<i64>,
49+
pub category_id: Option<String>,
50+
pub sort_by: Option<String>,
51+
pub sort_order: Option<String>,
52+
}
53+
54+
#[derive(Debug, Serialize)]
55+
pub struct PaginatedThreadsResponse {
56+
pub threads: Vec<CompleteThread>,
57+
pub total: i64,
58+
pub page: i64,
59+
pub page_size: i64,
60+
pub total_pages: i64,
4661
}
4762

4863
pub async fn handle_tickets_bot(
@@ -177,27 +192,90 @@ pub async fn handle_tickets_bot(
177192
return (StatusCode::OK, Json(serde_json::json!(complete)));
178193
}
179194

180-
let threads_query = match sqlx::query!(
195+
let page = params.page.unwrap_or(1).max(1);
196+
let page_size = params.page_size.unwrap_or(50).min(200).max(1);
197+
let offset = (page - 1) * page_size;
198+
199+
let status_filter = params.status.unwrap_or(0);
200+
201+
let mut where_conditions = vec![format!("status = {}", status_filter)];
202+
203+
if let Some(ref cat_id) = params.category_id {
204+
where_conditions.push(format!("category_id = '{}'", cat_id.replace("'", "''")));
205+
}
206+
207+
let where_clause = where_conditions.join(" AND ");
208+
209+
let sort_column = match params.sort_by.as_deref() {
210+
Some("user_name") => "user_name",
211+
Some("closed_at") => "closed_at",
212+
Some("created_at") => "created_at",
213+
_ => "created_at",
214+
};
215+
216+
let sort_order = match params.sort_order.as_deref() {
217+
Some("asc") => "ASC",
218+
_ => "DESC",
219+
};
220+
221+
let count_query = format!("SELECT COUNT(*) as count FROM threads WHERE {}", where_clause);
222+
let total: i64 = match sqlx::query_scalar(&count_query)
223+
.fetch_one(&db_pool)
224+
.await
225+
{
226+
Ok(count) => count,
227+
Err(err) => {
228+
eprintln!("Erreur SQL count: {:?}", err);
229+
return (
230+
StatusCode::INTERNAL_SERVER_ERROR,
231+
Json(serde_json::json!({
232+
"error": "Failed to count threads"
233+
})),
234+
);
235+
}
236+
};
237+
238+
let total_pages = (total as f64 / page_size as f64).ceil() as i64;
239+
240+
let query_str = format!(
181241
r#"
182242
SELECT
183243
id,
184244
user_id,
185245
user_name,
186246
channel_id,
187-
strftime('%s', created_at) as "created_at: Option<String>",
247+
strftime('%s', created_at) as created_at,
188248
next_message_number as new_message_number,
189249
status,
190250
user_left,
191-
strftime('%s', closed_at) as "closed_at: Option<String>",
251+
strftime('%s', closed_at) as closed_at,
192252
closed_by,
193253
category_id,
194254
category_name,
195255
required_permissions
196256
FROM threads
197-
WHERE status = 0
198-
ORDER BY closed_at DESC
199-
"#
200-
)
257+
WHERE {}
258+
ORDER BY {} {}
259+
LIMIT {} OFFSET {}
260+
"#,
261+
where_clause, sort_column, sort_order, page_size, offset
262+
);
263+
264+
let threads_query = match sqlx::query_as::<_, (
265+
String,
266+
i64,
267+
String,
268+
String,
269+
Option<String>,
270+
Option<i64>,
271+
i64,
272+
bool,
273+
Option<String>,
274+
Option<String>,
275+
Option<String>,
276+
Option<String>,
277+
Option<String>,
278+
)>(&query_str)
201279
.fetch_all(&db_pool)
202280
.await
203281
{
@@ -215,72 +293,99 @@ pub async fn handle_tickets_bot(
215293

216294
let mut threads: Vec<CompleteThread> = Vec::new();
217295

218-
for thread in threads_query {
219-
let messages_query = match sqlx::query!(
220-
r#"
221-
SELECT
222-
id,
223-
thread_id,
224-
user_id,
225-
user_name,
226-
is_anonymous,
227-
dm_message_id,
228-
inbox_message_id,
229-
message_number,
230-
created_at as "created_at: String",
231-
content
232-
FROM thread_messages
233-
WHERE thread_id = ?
234-
ORDER BY created_at ASC
235-
"#,
236-
thread.id
237-
)
238-
.fetch_all(&db_pool)
239-
.await
240-
{
241-
Ok(rows) => rows,
242-
Err(err) => {
243-
eprintln!("Erreur SQL messages pour {}: {:?}", thread.id, err);
244-
Vec::new()
245-
}
246-
};
296+
let thread_ids: Vec<String> = threads_query.iter().map(|t| t.0.clone()).collect();
247297

248-
let messages: Vec<ThreadMessage> = messages_query
249-
.into_iter()
250-
.map(|m| ThreadMessage {
251-
id: m.id,
252-
thread_id: m.thread_id,
253-
user_id: m.user_id,
254-
user_name: m.user_name,
255-
is_anonymous: m.is_anonymous,
256-
dm_message_id: m.dm_message_id,
257-
inbox_message_id: m.inbox_message_id,
258-
message_number: m.message_number,
259-
created_at: m.created_at,
260-
content: m.content,
261-
})
262-
.collect();
298+
let placeholders = thread_ids.iter().map(|_| "?").collect::<Vec<_>>().join(",");
299+
let messages_query_str = format!(
300+
r#"
301+
SELECT
302+
id,
303+
thread_id,
304+
user_id,
305+
user_name,
306+
is_anonymous,
307+
dm_message_id,
308+
inbox_message_id,
309+
message_number,
310+
created_at,
311+
content
312+
FROM thread_messages
313+
WHERE thread_id IN ({})
314+
ORDER BY thread_id, created_at ASC
315+
"#,
316+
placeholders
317+
);
318+
319+
let mut messages_query = sqlx::query_as::<_, (
320+
i64,
321+
String,
322+
i64,
323+
String,
324+
bool,
325+
Option<String>,
326+
Option<String>,
327+
Option<i64>,
328+
String,
329+
String,
330+
)>(&messages_query_str);
331+
332+
for thread_id in &thread_ids {
333+
messages_query = messages_query.bind(thread_id);
334+
}
335+
336+
let all_messages = messages_query.fetch_all(&db_pool).await.unwrap_or_else(|err| {
337+
eprintln!("Erreur SQL messages batch: {:?}", err);
338+
Vec::new()
339+
});
340+
341+
let mut messages_by_thread: std::collections::HashMap<String, Vec<ThreadMessage>> =
342+
std::collections::HashMap::new();
343+
344+
for msg in all_messages {
345+
messages_by_thread.entry(msg.1.clone()).or_insert_with(Vec::new).push(ThreadMessage {
346+
id: msg.0,
347+
thread_id: msg.1.clone(),
348+
user_id: msg.2,
349+
user_name: msg.3,
350+
is_anonymous: msg.4,
351+
dm_message_id: msg.5,
352+
inbox_message_id: msg.6,
353+
message_number: msg.7,
354+
created_at: msg.8,
355+
content: msg.9,
356+
});
357+
}
358+
359+
for thread in threads_query {
360+
let messages = messages_by_thread.get(&thread.0).cloned().unwrap_or_default();
263361

264362
threads.push(CompleteThread {
265-
id: thread.id,
266-
user_id: thread.user_id,
267-
user_name: thread.user_name,
268-
channel_id: thread.channel_id,
269-
created_at: thread.created_at
270-
.flatten()
363+
id: thread.0.clone(),
364+
user_id: thread.1,
365+
user_name: thread.2,
366+
channel_id: thread.3,
367+
created_at: thread.4
271368
.and_then(|ts: String| ts.parse::<i64>().ok())
272369
.unwrap_or_default(),
273-
new_message_number: thread.new_message_number.unwrap_or_default(),
274-
status: thread.status,
275-
user_left: thread.user_left,
276-
closed_at: thread.closed_at.flatten().and_then(|ts: String| ts.parse::<i64>().ok()),
277-
closed_by: thread.closed_by,
278-
category_id: thread.category_id,
279-
category_name: thread.category_name,
280-
required_permissions: thread.required_permissions,
370+
new_message_number: thread.5.unwrap_or_default(),
371+
status: thread.6,
372+
user_left: thread.7,
373+
closed_at: thread.8.and_then(|ts: String| ts.parse::<i64>().ok()),
374+
closed_by: thread.9,
375+
category_id: thread.10,
376+
category_name: thread.11,
377+
required_permissions: thread.12,
281378
messages,
282379
});
283380
}
284381

285-
(StatusCode::OK, Json(serde_json::json!(threads)))
382+
let response = PaginatedThreadsResponse {
383+
threads,
384+
total,
385+
page,
386+
page_size,
387+
total_pages,
388+
};
389+
390+
(StatusCode::OK, Json(serde_json::json!(response)))
286391
}

rustmail/src/handlers/ready_handler.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ fn update_threads_status(ctx: &Context, pool: &SqlitePool) {
111111
let ctx = ctx.clone();
112112
let ticket = ticket.clone();
113113
let handle = tokio::spawn(async move {
114-
println!("Update");
115114
if let Err(e) = update_thread_status_ui(&ctx, &ticket).await {
116115
eprintln!(
117116
"Failed to update thread status for channel {}: {:?}",

0 commit comments

Comments
 (0)