send up some changes to handlers

This commit is contained in:
dal 2025-03-04 07:47:53 -07:00
parent 8232f628ea
commit 8d50adce47
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
8 changed files with 261 additions and 5 deletions

View File

@ -43,7 +43,6 @@ let mut conn = get_pg_pool().get().await?;
### Concurrency Guidelines
- Prioritize concurrent operations, especially for:
- API requests
- Database transactions
- File operations
- Optimize database connection usage:
- Batch operations where possible

View File

@ -1,6 +1,6 @@
---
description: This is helpul docs for buildng hanlders in the project.
globs: ibs/handlers/**/*.rs
description: This is helpul docs for buildng hanlders in the project.l
globs: libs/handlers/**/*.rs
alwaysApply: false
---
# Handler Rules and Best Practices
@ -59,7 +59,6 @@ match operation() {
### Database Operations
- Use the connection pool: `get_pg_pool().get().await?`
- Run concurrent operations when possible
- Use transactions for related operations
- Handle database-specific errors appropriately
- Example:
```rust

View File

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

View File

@ -1,9 +1,13 @@
pub mod get_chat_handler;
pub mod post_chat_handler;
pub mod update_chats_handler;
pub mod delete_chats_handler;
pub mod types;
pub mod streaming_parser;
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 types::*;
pub use streaming_parser::StreamingParser;

View File

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

View File

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

View File

@ -1,13 +1,17 @@
use axum::{
routing::{get, post},
routing::{get, post, put, delete},
Router,
};
mod get_chat;
mod post_chat;
mod update_chats;
mod delete_chats;
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))
}

View File

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