list endpoints for chats and logs

This commit is contained in:
dal 2025-03-11 12:30:36 -06:00
parent fbfea49253
commit f6b42ec9b7
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
8 changed files with 199 additions and 30 deletions

View File

@ -10,7 +10,7 @@ use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize)]
pub struct ListChatsRequest {
pub page_token: Option<String>,
pub page: Option<i32>,
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<String>,
pub next_page: Option<i32>,
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<ListChatsResponse> {
) -> Result<Vec<ChatListItem>> {
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,
})
}
Ok(items)
}

View File

@ -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;

View File

@ -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<i32>,
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<String>,
pub last_edited: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PaginationInfo {
pub has_more: bool,
pub next_page: Option<i32>,
pub total_items: i32, // Number of items in current page
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ListLogsResponse {
pub items: Vec<LogListItem>,
pub pagination: PaginationInfo,
}
#[derive(Queryable)]
struct ChatWithUser {
// Chat fields
pub id: Uuid,
pub title: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub created_by: Uuid,
// User fields
pub user_name: Option<String>,
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<Vec<LogListItem>, 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<ChatWithUser> = query
.select((
chats::id,
chats::title,
chats::created_at,
chats::updated_at,
chats::created_by,
users::name.nullable(),
users::attributes,
))
.load::<ChatWithUser>(&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<LogListItem> = 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)
}

View File

@ -0,0 +1,3 @@
pub mod list_logs_handler;
pub use list_logs_handler::*;

View File

@ -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<String>,
pub page: Option<i32>,
#[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<AuthenticatedUser>,
Query(query): Query<ListChatsQuery>,
) -> Result<ApiResponse<ListChatsResponse>, (StatusCode, &'static str)> {
) -> Result<ApiResponse<Vec<ChatListItem>>, (StatusCode, &'static str)> {
let request = ListChatsRequest {
page_token: query.page_token,
page: query.page,
page_size: query.page_size,
admin_view: query.admin_view,
};

View File

@ -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<i32>,
#[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<AuthenticatedUser>,
Query(query): Query<ListLogsQuery>,
) -> Result<ApiResponse<Vec<LogListItem>>, (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"))
}
}
}

View File

@ -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))
}

View File

@ -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)),
)
}