mirror of https://github.com/buster-so/buster.git
send up some changes to handlers
This commit is contained in:
parent
8232f628ea
commit
8d50adce47
|
@ -43,7 +43,6 @@ let mut conn = get_pg_pool().get().await?;
|
||||||
### Concurrency Guidelines
|
### Concurrency Guidelines
|
||||||
- Prioritize concurrent operations, especially for:
|
- Prioritize concurrent operations, especially for:
|
||||||
- API requests
|
- API requests
|
||||||
- Database transactions
|
|
||||||
- File operations
|
- File operations
|
||||||
- Optimize database connection usage:
|
- Optimize database connection usage:
|
||||||
- Batch operations where possible
|
- Batch operations where possible
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
description: This is helpul docs for buildng hanlders in the project.
|
description: This is helpul docs for buildng hanlders in the project.l
|
||||||
globs: ibs/handlers/**/*.rs
|
globs: libs/handlers/**/*.rs
|
||||||
alwaysApply: false
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
# Handler Rules and Best Practices
|
# Handler Rules and Best Practices
|
||||||
|
@ -59,7 +59,6 @@ match operation() {
|
||||||
### Database Operations
|
### Database Operations
|
||||||
- Use the connection pool: `get_pg_pool().get().await?`
|
- Use the connection pool: `get_pg_pool().get().await?`
|
||||||
- Run concurrent operations when possible
|
- Run concurrent operations when possible
|
||||||
- Use transactions for related operations
|
|
||||||
- Handle database-specific errors appropriately
|
- Handle database-specific errors appropriately
|
||||||
- Example:
|
- Example:
|
||||||
```rust
|
```rust
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use chrono::Utc;
|
||||||
|
use database::models::{Chat, User};
|
||||||
|
use database::pool::get_pg_pool;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ChatDeleteResult {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub success: bool,
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bulk delete chats (soft delete by setting deleted_at)
|
||||||
|
///
|
||||||
|
/// This function efficiently soft deletes multiple chats using a bulk update operation.
|
||||||
|
/// It validates that the user has permission to delete each chat (they must be the creator).
|
||||||
|
///
|
||||||
|
/// Returns a list of results indicating success or failure for each chat.
|
||||||
|
pub async fn delete_chats_handler(
|
||||||
|
chat_ids: Vec<Uuid>,
|
||||||
|
user_id: &Uuid,
|
||||||
|
) -> Result<Vec<ChatDeleteResult>> {
|
||||||
|
use database::schema::chats;
|
||||||
|
|
||||||
|
// If no chat IDs provided, return empty result
|
||||||
|
if chat_ids.is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let pool = get_pg_pool();
|
||||||
|
let mut conn = pool.get().await?;
|
||||||
|
|
||||||
|
// Find all chats that the user has permission to delete in one query
|
||||||
|
let user_chats: Vec<Chat> = chats::table
|
||||||
|
.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)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Create a set of authorized chat IDs for quick lookup
|
||||||
|
let authorized_chat_ids: HashSet<Uuid> =
|
||||||
|
user_chats.iter().map(|c| c.id).collect();
|
||||||
|
|
||||||
|
// Prepare results for unauthorized chats
|
||||||
|
let mut delete_results: Vec<ChatDeleteResult> = chat_ids
|
||||||
|
.iter()
|
||||||
|
.filter(|id| !authorized_chat_ids.contains(id))
|
||||||
|
.map(|id| ChatDeleteResult {
|
||||||
|
id: *id,
|
||||||
|
success: false,
|
||||||
|
error: Some("Chat not found or you don't have permission to delete it".to_string()),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// If we have authorized deletes, perform them in bulk
|
||||||
|
if !authorized_chat_ids.is_empty() {
|
||||||
|
let authorized_ids: Vec<Uuid> = authorized_chat_ids.into_iter().collect();
|
||||||
|
|
||||||
|
// Perform a bulk update for all authorized chats at once
|
||||||
|
let result = diesel::update(chats::table)
|
||||||
|
.filter(chats::id.eq_any(authorized_ids.clone()))
|
||||||
|
.set((
|
||||||
|
chats::deleted_at.eq(Some(Utc::now())),
|
||||||
|
chats::updated_at.eq(Utc::now()),
|
||||||
|
))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => {
|
||||||
|
// Add success results for all authorized chats
|
||||||
|
let success_results: Vec<ChatDeleteResult> = authorized_ids
|
||||||
|
.into_iter()
|
||||||
|
.map(|id| ChatDeleteResult {
|
||||||
|
id,
|
||||||
|
success: true,
|
||||||
|
error: None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
delete_results.extend(success_results);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
// Add error results for all authorized chats
|
||||||
|
let error_results: Vec<ChatDeleteResult> = authorized_ids
|
||||||
|
.into_iter()
|
||||||
|
.map(|id| ChatDeleteResult {
|
||||||
|
id,
|
||||||
|
success: false,
|
||||||
|
error: Some(format!("Failed to delete chat: {}", e)),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
delete_results.extend(error_results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(delete_results)
|
||||||
|
}
|
|
@ -1,9 +1,13 @@
|
||||||
pub mod get_chat_handler;
|
pub mod get_chat_handler;
|
||||||
pub mod post_chat_handler;
|
pub mod post_chat_handler;
|
||||||
|
pub mod update_chats_handler;
|
||||||
|
pub mod delete_chats_handler;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod streaming_parser;
|
pub mod streaming_parser;
|
||||||
|
|
||||||
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 delete_chats_handler::delete_chats_handler;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
pub use streaming_parser::StreamingParser;
|
pub use streaming_parser::StreamingParser;
|
|
@ -0,0 +1,99 @@
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use chrono::Utc;
|
||||||
|
use database::models::{Chat, User};
|
||||||
|
use database::pool::get_pg_pool;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::pg::expression::dsl::any;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use futures::future::try_join_all;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ChatUpdate {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ChatUpdateResult {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub success: bool,
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bulk update chat titles
|
||||||
|
///
|
||||||
|
/// This function efficiently updates the titles of multiple chats.
|
||||||
|
/// It validates that the user has permission to update each chat (they must be the creator)
|
||||||
|
/// in a single database query, then performs individual updates for each chat.
|
||||||
|
///
|
||||||
|
/// Returns a list of results indicating success or failure for each chat.
|
||||||
|
pub async fn update_chats_handler(
|
||||||
|
updates: Vec<ChatUpdate>,
|
||||||
|
user_id: &Uuid,
|
||||||
|
) -> Result<Vec<ChatUpdateResult>> {
|
||||||
|
use database::schema::chats;
|
||||||
|
|
||||||
|
// If no updates provided, return empty result
|
||||||
|
if updates.is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let pool = get_pg_pool();
|
||||||
|
let mut conn = pool.get().await?;
|
||||||
|
|
||||||
|
// Extract all chat IDs
|
||||||
|
let chat_ids: Vec<Uuid> = updates.iter().map(|u| u.id).collect();
|
||||||
|
|
||||||
|
// 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::created_by.eq(user_id))
|
||||||
|
.filter(chats::deleted_at.is_null())
|
||||||
|
.load::<Chat>(&mut conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Create a set of authorized chat IDs for quick lookup
|
||||||
|
let authorized_chat_ids: HashSet<Uuid> =
|
||||||
|
user_chats.iter().map(|c| c.id).collect();
|
||||||
|
|
||||||
|
let mut update_results = Vec::with_capacity(updates.len());
|
||||||
|
|
||||||
|
// Process each update
|
||||||
|
for update in updates {
|
||||||
|
if authorized_chat_ids.contains(&update.id) {
|
||||||
|
// Update the chat title
|
||||||
|
let result = diesel::update(chats::table)
|
||||||
|
.filter(chats::id.eq(update.id))
|
||||||
|
.set((
|
||||||
|
chats::title.eq(update.title.clone()),
|
||||||
|
chats::updated_at.eq(Utc::now()),
|
||||||
|
))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => update_results.push(ChatUpdateResult {
|
||||||
|
id: update.id,
|
||||||
|
success: true,
|
||||||
|
error: None,
|
||||||
|
}),
|
||||||
|
Err(e) => update_results.push(ChatUpdateResult {
|
||||||
|
id: update.id,
|
||||||
|
success: false,
|
||||||
|
error: Some(format!("Failed to update chat: {}", e)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
update_results.push(ChatUpdateResult {
|
||||||
|
id: update.id,
|
||||||
|
success: false,
|
||||||
|
error: Some("Chat not found or you don't have permission to update it".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(update_results)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::Extension;
|
||||||
|
use axum::Json;
|
||||||
|
use database::models::User;
|
||||||
|
use handlers::chats::delete_chats_handler::{ChatDeleteResult};
|
||||||
|
use handlers::chats::delete_chats_handler;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::routes::rest::ApiResponse;
|
||||||
|
|
||||||
|
pub async fn delete_chats_route(
|
||||||
|
Extension(user): Extension<User>,
|
||||||
|
Json(chat_ids): Json<Vec<Uuid>>,
|
||||||
|
) -> Result<ApiResponse<Vec<ChatDeleteResult>>, (StatusCode, &'static str)> {
|
||||||
|
match delete_chats_handler(chat_ids, &user.id).await {
|
||||||
|
Ok(results) => Ok(ApiResponse::JsonData(results)),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error deleting chats: {}", e);
|
||||||
|
Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to delete chats"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,17 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
routing::{get, post},
|
routing::{get, post, put, delete},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod get_chat;
|
mod get_chat;
|
||||||
mod post_chat;
|
mod post_chat;
|
||||||
|
mod update_chats;
|
||||||
|
mod delete_chats;
|
||||||
|
|
||||||
pub fn router() -> Router {
|
pub fn router() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", post(post_chat::post_chat_route))
|
.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("/:id", get(get_chat::get_chat_route))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::Extension;
|
||||||
|
use axum::Json;
|
||||||
|
use database::models::User;
|
||||||
|
use handlers::chats::update_chats_handler::{ChatUpdate, ChatUpdateResult};
|
||||||
|
use handlers::chats::update_chats_handler;
|
||||||
|
|
||||||
|
use crate::routes::rest::ApiResponse;
|
||||||
|
|
||||||
|
pub async fn update_chats_route(
|
||||||
|
Extension(user): Extension<User>,
|
||||||
|
Json(updates): Json<Vec<ChatUpdate>>,
|
||||||
|
) -> Result<ApiResponse<Vec<ChatUpdateResult>>, (StatusCode, &'static str)> {
|
||||||
|
match update_chats_handler(updates, &user.id).await {
|
||||||
|
Ok(results) => Ok(ApiResponse::JsonData(results)),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error updating chats: {}", e);
|
||||||
|
Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to update chats"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue