mirror of https://github.com/buster-so/buster.git
list is good for now, will adjust later.
This commit is contained in:
parent
5af9a8e4eb
commit
b466a061c4
|
@ -21,6 +21,7 @@ regex = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
|
base64 = { workspace = true }
|
||||||
|
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
database = { path = "../database" }
|
database = { path = "../database" }
|
||||||
|
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
|
@ -5,10 +5,12 @@ pub mod delete_chats_handler;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod streaming_parser;
|
pub mod streaming_parser;
|
||||||
pub mod context_loaders;
|
pub mod context_loaders;
|
||||||
|
pub mod list_chats_handler;
|
||||||
|
|
||||||
pub use get_chat_handler::get_chat_handler;
|
pub use get_chat_handler::get_chat_handler;
|
||||||
pub use post_chat_handler::post_chat_handler;
|
pub use post_chat_handler::post_chat_handler;
|
||||||
pub use update_chats_handler::update_chats_handler;
|
pub use update_chats_handler::update_chats_handler;
|
||||||
pub use delete_chats_handler::delete_chats_handler;
|
pub use delete_chats_handler::delete_chats_handler;
|
||||||
|
pub use list_chats_handler::list_chats_handler;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
pub use streaming_parser::StreamingParser;
|
pub use streaming_parser::StreamingParser;
|
|
@ -49,7 +49,7 @@ pub async fn update_chats_handler(
|
||||||
|
|
||||||
// Find all chats that the user has permission to update in one query
|
// Find all chats that the user has permission to update in one query
|
||||||
let user_chats: Vec<Chat> = chats::table
|
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::created_by.eq(user_id))
|
||||||
.filter(chats::deleted_at.is_null())
|
.filter(chats::deleted_at.is_null())
|
||||||
.load::<Chat>(&mut conn)
|
.load::<Chat>(&mut conn)
|
||||||
|
|
|
@ -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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,15 +3,23 @@ use axum::{
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod delete_chats;
|
||||||
mod get_chat;
|
mod get_chat;
|
||||||
|
mod list_chats;
|
||||||
mod post_chat;
|
mod post_chat;
|
||||||
mod update_chats;
|
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 {
|
pub fn router() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", post(post_chat::post_chat_route))
|
.route("/", get(list_chats_route))
|
||||||
.route("/", put(update_chats::update_chats_route))
|
.route("/", post(post_chat_route))
|
||||||
.route("/", delete(delete_chats::delete_chats_route))
|
.route("/", put(update_chats_route))
|
||||||
.route("/:id", get(get_chat::get_chat_route))
|
.route("/", delete(delete_chats_route))
|
||||||
|
.route("/:id", get(get_chat_route))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue