diff --git a/api/libs/handlers/src/chats/list_chats_handler.rs b/api/libs/handlers/src/chats/list_chats_handler.rs index 880773fdd..a734f9266 100644 --- a/api/libs/handlers/src/chats/list_chats_handler.rs +++ b/api/libs/handlers/src/chats/list_chats_handler.rs @@ -10,7 +10,7 @@ use uuid::Uuid; #[derive(Debug, Serialize, Deserialize)] pub struct ListChatsRequest { - pub page_token: Option, + pub page: Option, pub page_size: i32, pub admin_view: bool, } @@ -32,7 +32,7 @@ pub struct ChatListItem { #[derive(Debug, Serialize, Deserialize)] pub struct PaginationInfo { pub has_more: bool, - pub next_page_token: Option, + pub next_page: Option, pub total_items: i32, // Number of items in current page } @@ -58,14 +58,14 @@ struct ChatWithUser { /// List chats with pagination support /// /// This function efficiently retrieves a list of chats with their associated user information. -/// It supports pagination using cursor-based pagination (page_token) and limits results using page_size. +/// It supports pagination using page number and limits results using page_size. /// If admin_view is true and the user has admin privileges, it shows all chats; otherwise, only the user's chats. /// /// Returns a list of chat items with user information and pagination details. pub async fn list_chats_handler( request: ListChatsRequest, user_id: &Uuid, -) -> Result { +) -> Result> { use database::schema::{chats, users}; let mut conn = get_pg_pool().get().await?; @@ -81,19 +81,14 @@ pub async fn list_chats_handler( query = query.filter(chats::created_by.eq(user_id)); } - // Add cursor-based pagination if page_token is provided - if let Some(token) = request.page_token { - // Parse the RFC3339 timestamp directly - let cursor_dt = DateTime::parse_from_rfc3339(&token) - .map_err(|_| anyhow!("Invalid timestamp in page token"))? - .with_timezone(&Utc); - - query = query.filter(chats::created_at.lt(cursor_dt)); - } + // Calculate offset based on page number + let page = request.page.unwrap_or(1); + let offset = (page - 1) * request.page_size; - // Order by creation date descending and limit results + // Order by creation date descending and apply pagination query = query .order_by(chats::created_at.desc()) + .offset(offset as i64) .limit((request.page_size + 1) as i64); // Execute query and select required fields @@ -139,17 +134,9 @@ pub async fn list_chats_handler( // Create pagination info let pagination = PaginationInfo { has_more, - next_page_token: if has_more { - // Just use the RFC3339 timestamp directly as the token - items.last().map(|last_item| last_item.created_at.clone()) - } else { - None - }, + next_page: if has_more { Some(page + 1) } else { None }, total_items: items.len() as i32, }; - Ok(ListChatsResponse { - items, - pagination, - }) -} \ No newline at end of file + Ok(items) +} \ No newline at end of file diff --git a/api/libs/handlers/src/lib.rs b/api/libs/handlers/src/lib.rs index fce191eff..08f66bd27 100644 --- a/api/libs/handlers/src/lib.rs +++ b/api/libs/handlers/src/lib.rs @@ -2,6 +2,7 @@ pub mod chats; pub mod collections; pub mod favorites; pub mod files; +pub mod logs; pub mod messages; pub mod metrics; diff --git a/api/libs/handlers/src/logs/list_logs_handler.rs b/api/libs/handlers/src/logs/list_logs_handler.rs new file mode 100644 index 000000000..b95c4277b --- /dev/null +++ b/api/libs/handlers/src/logs/list_logs_handler.rs @@ -0,0 +1,132 @@ +use chrono::{DateTime, Utc}; +use database::pool::get_pg_pool; +use diesel::prelude::*; +use diesel::{ExpressionMethods, JoinOnDsl, QueryDsl}; +use diesel_async::RunQueryDsl; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use uuid::Uuid; + +#[derive(Debug, Serialize, Deserialize)] +pub struct ListLogsRequest { + pub page: Option, + pub page_size: i32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LogListItem { + pub id: String, + pub title: String, + pub updated_at: String, + pub created_at: String, + pub created_by: String, + pub created_by_id: String, + pub created_by_name: String, + pub created_by_avatar: Option, + pub last_edited: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PaginationInfo { + pub has_more: bool, + pub next_page: Option, + pub total_items: i32, // Number of items in current page +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ListLogsResponse { + pub items: Vec, + pub pagination: PaginationInfo, +} + +#[derive(Queryable)] +struct ChatWithUser { + // Chat fields + pub id: Uuid, + pub title: String, + pub created_at: DateTime, + pub updated_at: DateTime, + pub created_by: Uuid, + // User fields + pub user_name: Option, + pub user_attributes: Value, +} + +/// List logs with pagination support +/// +/// This function efficiently retrieves a list of chats (logs) with their associated user information. +/// It supports pagination using page number and limits results using page_size. +/// Unlike the regular chats endpoint, logs are not restricted to the user and are visible to everyone. +/// +/// Returns a list of log items with user information and pagination details. +pub async fn list_logs_handler( + request: ListLogsRequest, +) -> Result, anyhow::Error> { + use database::schema::{chats, users}; + + let mut conn = get_pg_pool().get().await?; + + // Start building the query + let mut query = chats::table + .inner_join(users::table.on(chats::created_by.eq(users::id))) + .filter(chats::deleted_at.is_null()) + .into_boxed(); + + // Calculate offset based on page number + let page = request.page.unwrap_or(1); + let offset = (page - 1) * request.page_size; + + // Order by creation date descending and apply pagination + query = query + .order_by(chats::created_at.desc()) + .offset(offset as i64) + .limit((request.page_size + 1) as i64); + + // Execute query and select required fields + let results: Vec = query + .select(( + chats::id, + chats::title, + chats::created_at, + chats::updated_at, + chats::created_by, + users::name.nullable(), + users::attributes, + )) + .load::(&mut conn) + .await?; + + // Check if there are more results and prepare pagination info + let has_more = results.len() > request.page_size as usize; + let items: Vec = results + .into_iter() + .take(request.page_size as usize) + .map(|chat| { + let created_by_avatar = chat.user_attributes + .get("avatar") + .and_then(|v| v.as_str()) + .map(String::from); + + LogListItem { + id: chat.id.to_string(), + title: chat.title, + created_at: chat.created_at.to_rfc3339(), + updated_at: chat.updated_at.to_rfc3339(), + created_by: chat.created_by.to_string(), + created_by_id: chat.created_by.to_string(), + created_by_name: chat.user_name.unwrap_or_else(|| "Unknown".to_string()), + created_by_avatar, + last_edited: chat.updated_at.to_rfc3339(), + } + }) + .collect(); + + // Create pagination info + let pagination = PaginationInfo { + has_more, + next_page: if has_more { Some(page + 1) } else { None }, + total_items: items.len() as i32, + }; + + Ok(items) +} diff --git a/api/libs/handlers/src/logs/mod.rs b/api/libs/handlers/src/logs/mod.rs new file mode 100644 index 000000000..f30c6882b --- /dev/null +++ b/api/libs/handlers/src/logs/mod.rs @@ -0,0 +1,3 @@ +pub mod list_logs_handler; + +pub use list_logs_handler::*; diff --git a/api/src/routes/rest/routes/chats/list_chats.rs b/api/src/routes/rest/routes/chats/list_chats.rs index 17fce5113..5d162998a 100644 --- a/api/src/routes/rest/routes/chats/list_chats.rs +++ b/api/src/routes/rest/routes/chats/list_chats.rs @@ -1,16 +1,16 @@ use axum::{extract::Query, http::StatusCode, Extension}; use database::models::User; use handlers::chats::list_chats_handler::{ - list_chats_handler, ListChatsRequest, ListChatsResponse, + list_chats_handler, ChatListItem, ListChatsRequest, ListChatsResponse, }; -use serde::Deserialize; use middleware::AuthenticatedUser; +use serde::Deserialize; use crate::routes::rest::ApiResponse; #[derive(Debug, Deserialize)] pub struct ListChatsQuery { - pub page_token: Option, + pub page: Option, #[serde(default = "default_page_size")] pub page_size: i32, #[serde(default)] @@ -24,9 +24,9 @@ fn default_page_size() -> i32 { pub async fn list_chats_route( Extension(user): Extension, Query(query): Query, -) -> Result, (StatusCode, &'static str)> { +) -> Result>, (StatusCode, &'static str)> { let request = ListChatsRequest { - page_token: query.page_token, + page: query.page, page_size: query.page_size, admin_view: query.admin_view, }; diff --git a/api/src/routes/rest/routes/logs/list_logs.rs b/api/src/routes/rest/routes/logs/list_logs.rs new file mode 100644 index 000000000..8b8a61854 --- /dev/null +++ b/api/src/routes/rest/routes/logs/list_logs.rs @@ -0,0 +1,37 @@ +use axum::{extract::Query, http::StatusCode, Extension}; +use handlers::logs::list_logs_handler::{ + list_logs_handler, ListLogsRequest, ListLogsResponse, LogListItem, +}; +use middleware::AuthenticatedUser; +use serde::Deserialize; + +use crate::routes::rest::ApiResponse; + +#[derive(Debug, Deserialize)] +pub struct ListLogsQuery { + pub page: Option, + #[serde(default = "default_page_size")] + pub page_size: i32, +} + +fn default_page_size() -> i32 { + 20 // Default to 20 items per page +} + +pub async fn list_logs_route( + Extension(user): Extension, + Query(query): Query, +) -> Result>, (StatusCode, &'static str)> { + let request = ListLogsRequest { + page: query.page, + page_size: query.page_size, + }; + + match list_logs_handler(request).await { + Ok(response) => Ok(ApiResponse::JsonData(response)), + Err(e) => { + tracing::error!("Error listing logs: {}", e); + Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to list logs")) + } + } +} diff --git a/api/src/routes/rest/routes/logs/mod.rs b/api/src/routes/rest/routes/logs/mod.rs new file mode 100644 index 000000000..750b5e35e --- /dev/null +++ b/api/src/routes/rest/routes/logs/mod.rs @@ -0,0 +1,7 @@ +use axum::{routing::get, Router}; + +mod list_logs; + +pub fn router() -> Router { + Router::new().route("/", get(list_logs::list_logs_route)) +} diff --git a/api/src/routes/rest/routes/mod.rs b/api/src/routes/rest/routes/mod.rs index fae942cf0..86dac2ad4 100644 --- a/api/src/routes/rest/routes/mod.rs +++ b/api/src/routes/rest/routes/mod.rs @@ -5,6 +5,7 @@ mod dashboards; mod data_sources; mod dataset_groups; mod datasets; +mod logs; mod messages; mod metrics; mod organizations; @@ -33,6 +34,7 @@ pub fn router() -> Router { .nest("/dashboards", dashboards::router()) .nest("/users", users::router()) .nest("/collections", collections::router()) + .nest("/logs", logs::router()) .route_layer(axum_middleware::from_fn(auth)), ) }