list is good for now, will adjust later.

This commit is contained in:
dal 2025-03-05 12:47:08 -07:00
parent 5af9a8e4eb
commit b466a061c4
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
6 changed files with 205 additions and 6 deletions

View File

@ -21,6 +21,7 @@ regex = { workspace = true }
indexmap = { workspace = true }
async-trait = { workspace = true }
once_cell = { workspace = true }
base64 = { workspace = true }
# Local dependencies
database = { path = "../database" }

View File

@ -0,0 +1,148 @@
use anyhow::{anyhow, Result};
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 ListChatsRequest {
pub page_token: Option<String>,
pub page_size: i32,
pub admin_view: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ChatListItem {
pub id: String,
pub title: String,
pub is_favorited: bool,
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 ListChatsResponse {
pub items: Vec<ChatListItem>,
pub next_page_token: Option<String>,
}
#[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 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.
/// 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> {
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();
// Add user filter if not admin view
if !request.admin_view {
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 {
let decoded_token = base64::decode(&token)
.map_err(|_| anyhow!("Invalid page token"))?;
let cursor_timestamp = String::from_utf8(decoded_token)
.map_err(|_| anyhow!("Invalid page token format"))?;
let cursor_dt = DateTime::parse_from_rfc3339(&cursor_timestamp)
.map_err(|_| anyhow!("Invalid timestamp in page token"))?
.with_timezone(&Utc);
query = query.filter(chats::created_at.lt(cursor_dt));
}
// Order by creation date descending and limit results
query = query
.order_by(chats::created_at.desc())
.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
let has_more = results.len() > request.page_size as usize;
let mut items = 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);
ChatListItem {
id: chat.id.to_string(),
title: chat.title,
is_favorited: false, // TODO: Implement favorites feature
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::<Vec<_>>();
// Generate next page token if there are more results
let next_page_token = if has_more {
items
.last()
.map(|last_item| base64::encode(&last_item.created_at))
} else {
None
};
Ok(ListChatsResponse {
items,
next_page_token,
})
}

View File

@ -5,10 +5,12 @@ pub mod delete_chats_handler;
pub mod types;
pub mod streaming_parser;
pub mod context_loaders;
pub mod list_chats_handler;
pub use get_chat_handler::get_chat_handler;
pub use post_chat_handler::post_chat_handler;
pub use update_chats_handler::update_chats_handler;
pub use delete_chats_handler::delete_chats_handler;
pub use list_chats_handler::list_chats_handler;
pub use types::*;
pub use streaming_parser::StreamingParser;

View File

@ -49,7 +49,7 @@ pub async fn update_chats_handler(
// Find all chats that the user has permission to update in one query
let user_chats: Vec<Chat> = chats::table
.filter(chats::id.eq(any(chat_ids.clone())))
.filter(chats::id.eq_any(chat_ids.clone()))
.filter(chats::created_by.eq(user_id))
.filter(chats::deleted_at.is_null())
.load::<Chat>(&mut conn)

View File

@ -0,0 +1,40 @@
use axum::{extract::Query, http::StatusCode, Extension};
use database::models::User;
use handlers::chats::list_chats_handler::{
list_chats_handler, ListChatsRequest, ListChatsResponse,
};
use serde::Deserialize;
use crate::routes::rest::ApiResponse;
#[derive(Debug, Deserialize)]
pub struct ListChatsQuery {
pub page_token: Option<String>,
#[serde(default = "default_page_size")]
pub page_size: i32,
#[serde(default)]
pub admin_view: bool,
}
fn default_page_size() -> i32 {
20 // Default to 20 items per page
}
pub async fn list_chats_route(
Extension(user): Extension<User>,
Query(query): Query<ListChatsQuery>,
) -> Result<ApiResponse<ListChatsResponse>, (StatusCode, &'static str)> {
let request = ListChatsRequest {
page_token: query.page_token,
page_size: query.page_size,
admin_view: query.admin_view,
};
match list_chats_handler(request, &user.id).await {
Ok(response) => Ok(ApiResponse::JsonData(response)),
Err(e) => {
tracing::error!("Error listing chats: {}", e);
Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to list chats"))
}
}
}

View File

@ -3,15 +3,23 @@ use axum::{
Router,
};
mod delete_chats;
mod get_chat;
mod list_chats;
mod post_chat;
mod update_chats;
mod delete_chats;
pub use delete_chats::delete_chats_route;
pub use get_chat::get_chat_route;
pub use list_chats::list_chats_route;
pub use post_chat::post_chat_route;
pub use update_chats::update_chats_route;
pub fn router() -> Router {
Router::new()
.route("/", post(post_chat::post_chat_route))
.route("/", put(update_chats::update_chats_route))
.route("/", delete(delete_chats::delete_chats_route))
.route("/:id", get(get_chat::get_chat_route))
.route("/", get(list_chats_route))
.route("/", post(post_chat_route))
.route("/", put(update_chats_route))
.route("/", delete(delete_chats_route))
.route("/:id", get(get_chat_route))
}