mirror of https://github.com/buster-so/buster.git
added in the message and chat updates
This commit is contained in:
parent
365e8429d2
commit
f5a239f615
|
@ -56,6 +56,7 @@ pub struct Message {
|
|||
pub updated_at: DateTime<Utc>,
|
||||
pub deleted_at: Option<DateTime<Utc>>,
|
||||
pub created_by: Uuid,
|
||||
pub feedback: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Insertable, Debug)]
|
||||
|
|
|
@ -334,6 +334,7 @@ diesel::table! {
|
|||
updated_at -> Timestamptz,
|
||||
deleted_at -> Nullable<Timestamptz>,
|
||||
created_by -> Uuid,
|
||||
feedback -> Nullable<Text>,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -369,6 +369,7 @@ pub async fn post_chat_handler(
|
|||
final_reasoning_message: format!("Reasoned for {} seconds", reasoning_duration),
|
||||
title: title.title.clone().unwrap_or_default(),
|
||||
raw_llm_messages: serde_json::to_value(&raw_llm_messages)?,
|
||||
feedback: None
|
||||
};
|
||||
|
||||
let mut conn = get_pg_pool().get().await?;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
pub mod delete_message_handler;
|
||||
pub mod update_message_handler;
|
||||
|
||||
pub use delete_message_handler::*;
|
||||
pub use update_message_handler::*;
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use chrono::Utc;
|
||||
use database::{pool::get_pg_pool, schema::messages};
|
||||
use diesel::prelude::*;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use middleware::AuthenticatedUser;
|
||||
use serde_json::{json, Value};
|
||||
use std::str::FromStr;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::messages::types::MessageFeedback;
|
||||
|
||||
/// Update a message with new properties
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `user` - The authenticated user
|
||||
/// * `message_id` - The ID of the message to update
|
||||
/// * `feedback` - Optional feedback for the message ("positive" or "negative")
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(())` - If the message was successfully updated
|
||||
/// * `Err(anyhow::Error)` - If there was an error updating the message
|
||||
pub async fn update_message_handler(
|
||||
user: AuthenticatedUser,
|
||||
message_id: Uuid,
|
||||
feedback: Option<String>,
|
||||
) -> Result<()> {
|
||||
let pool = get_pg_pool();
|
||||
let mut conn = pool.get().await?;
|
||||
|
||||
// Check if the message exists and belongs to the user
|
||||
let message_exists = diesel::dsl::select(diesel::dsl::exists(
|
||||
messages::table.filter(
|
||||
messages::id.eq(message_id)
|
||||
.and(messages::created_by.eq(user.id)),
|
||||
),
|
||||
))
|
||||
.get_result::<bool>(&mut conn)
|
||||
.await?;
|
||||
|
||||
if !message_exists {
|
||||
return Err(anyhow!("Message not found or you don't have permission to update it"));
|
||||
}
|
||||
|
||||
// Build update parameters - don't execute the set operation yet
|
||||
let update_statement = diesel::update(messages::table)
|
||||
.filter(messages::id.eq(message_id));
|
||||
|
||||
// Add feedback if provided
|
||||
if let Some(fb_str) = feedback {
|
||||
// Validate feedback value
|
||||
let feedback = MessageFeedback::from_str(&fb_str)
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
|
||||
// Update the feedback column directly
|
||||
update_statement
|
||||
.set((
|
||||
messages::updated_at.eq(Utc::now()),
|
||||
messages::feedback.eq(feedback.to_string())
|
||||
))
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
} else {
|
||||
// If no feedback, just update the timestamp
|
||||
update_statement
|
||||
.set(messages::updated_at.eq(Utc::now()))
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -5,6 +5,9 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::Value;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod message_feedback;
|
||||
pub use message_feedback::MessageFeedback;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ChatMessage {
|
||||
pub id: Uuid,
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents the feedback type for a message
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum MessageFeedback {
|
||||
/// Positive feedback
|
||||
Positive,
|
||||
/// Negative feedback
|
||||
Negative,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MessageFeedback {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MessageFeedback::Positive => write!(f, "positive"),
|
||||
MessageFeedback::Negative => write!(f, "negative"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for MessageFeedback {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"positive" => Ok(MessageFeedback::Positive),
|
||||
"negative" => Ok(MessageFeedback::Negative),
|
||||
_ => Err("Invalid feedback value. Must be 'positive' or 'negative'"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
alter table messages drop column feedback;
|
|
@ -0,0 +1,2 @@
|
|||
-- Your SQL goes here
|
||||
alter table messages add column feedback text null;
|
|
@ -0,0 +1,118 @@
|
|||
# Update Message REST Endpoint PRD
|
||||
|
||||
## Overview
|
||||
This PRD describes the implementation of a new REST endpoint for updating a message. This endpoint will allow users to update specific properties of a message, such as setting feedback on a message.
|
||||
|
||||
## Endpoint Details
|
||||
|
||||
### Basic Information
|
||||
- **HTTP Method**: PUT
|
||||
- **URL Path**: `/api/v1/messages/:id`
|
||||
- **Purpose**: Update properties of a specific message
|
||||
|
||||
### Request Format
|
||||
- **Headers**:
|
||||
- `Content-Type: application/json`
|
||||
- `Authorization: Bearer {token}`
|
||||
- **URL Parameters**:
|
||||
- `id`: UUID of the message to update
|
||||
- **Request Body (JSON)**:
|
||||
- `feedback`: String (optional) - "positive" or "negative"
|
||||
|
||||
### Response Format
|
||||
- **Success**:
|
||||
- Status: 200 OK
|
||||
- Body: Updated message object
|
||||
- **Error Responses**:
|
||||
- 401 Unauthorized: If the user is not authenticated
|
||||
- 403 Forbidden: If the user does not have permission to update the message
|
||||
- 404 Not Found: If the message with the specified ID does not exist
|
||||
- 500 Internal Server Error: If there's a server error
|
||||
|
||||
### Example Request
|
||||
```bash
|
||||
curl --location --request PUT 'http://localhost:3001/api/v1/messages/cee3065d-c165-4e04-be44-be05d1e6d902' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjMmRkNjRjZC1mN2YzLTQ4ODQtYmM5MS1kNDZhZTQzMTkwMWUiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MjUxODU1NTMyNiwiYXVkIjoiYXV0aGVudGljYXRlZCJ9.uRs5OVyYErQ1iSQwAXVUD6TFolOu31ejPhcBS41ResA' \
|
||||
--data '{
|
||||
"feedback": "negative"
|
||||
}'
|
||||
```
|
||||
|
||||
### Example Response
|
||||
```json
|
||||
{
|
||||
"id": "cee3065d-c165-4e04-be44-be05d1e6d902",
|
||||
"request_message": {
|
||||
"request": "Example message text",
|
||||
"sender_id": "c2dd64cd-f7f3-4884-bc91-d46ae431901e",
|
||||
"sender_name": "John Doe",
|
||||
"sender_avatar": null
|
||||
},
|
||||
"response_message_ids": ["..."],
|
||||
"response_messages": {},
|
||||
"reasoning_message_ids": ["..."],
|
||||
"reasoning_messages": {},
|
||||
"created_at": "2025-03-20T15:30:00Z",
|
||||
"final_reasoning_message": null,
|
||||
"feedback": "negative"
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### New Files to Create
|
||||
|
||||
#### 1. Handler Implementation
|
||||
- **File**: `/libs/handlers/src/messages/helpers/update_message_handler.rs`
|
||||
- **Purpose**: Contains the business logic for updating a message
|
||||
|
||||
#### 2. REST Route Implementation
|
||||
- **File**: `/src/routes/rest/routes/messages/update_message.rs`
|
||||
- **Purpose**: Contains the HTTP handler for the REST endpoint
|
||||
|
||||
#### 3. Update Module Exports
|
||||
- **File**: `/libs/handlers/src/messages/helpers/mod.rs` (update)
|
||||
- **File**: `/src/routes/rest/routes/messages/mod.rs` (update)
|
||||
|
||||
### Implementation Steps
|
||||
|
||||
#### 1. Create the Handler Function
|
||||
Create a new handler in `/libs/handlers/src/messages/helpers/update_message_handler.rs` that:
|
||||
- Takes a user, message ID, and update parameters
|
||||
- Verifies the user has permission to update the message
|
||||
- Updates the message in the database
|
||||
- Returns the updated message
|
||||
|
||||
#### 2. Create the REST Endpoint
|
||||
Create a new file at `/src/routes/rest/routes/messages/update_message.rs` that:
|
||||
- Creates a request struct to deserialize the request body
|
||||
- Maps the HTTP request to the handler function
|
||||
- Handles errors appropriately
|
||||
- Returns the appropriate HTTP response
|
||||
|
||||
#### 3. Update Module Exports
|
||||
Update the module exports to include the new handler and endpoint.
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Database Updates
|
||||
The update operation will modify records in the `messages` table.
|
||||
|
||||
### Expected Behavior
|
||||
- Only the user who created the message or an admin should be able to update the message
|
||||
- Updates should be atomic and consistent
|
||||
- The endpoint should validate input data before updating the database
|
||||
|
||||
### Testing Strategy
|
||||
1. Test updating a message with valid data as the message owner
|
||||
2. Test updating a message with valid data as an admin
|
||||
3. Test updating a message with invalid data
|
||||
4. Test updating a non-existent message
|
||||
5. Test updating a message without permission
|
||||
6. Test concurrent updates to the same message
|
||||
|
||||
## Security Considerations
|
||||
- Ensure proper authentication and authorization
|
||||
- Validate input data to prevent injection attacks
|
||||
- Ensure database queries are properly parameterized
|
|
@ -1,15 +1,19 @@
|
|||
use axum::{
|
||||
routing::delete,
|
||||
routing::{delete, put},
|
||||
Router,
|
||||
};
|
||||
|
||||
mod delete_message;
|
||||
mod update_message;
|
||||
|
||||
use delete_message::delete_message_rest_handler;
|
||||
use update_message::update_message_rest_handler;
|
||||
|
||||
/// Create a router for message-related endpoints
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
// Delete a message and all subsequent messages in the same chat
|
||||
.route("/:id", delete(delete_message_rest_handler))
|
||||
// Update a message
|
||||
.route("/:id", put(update_message_rest_handler))
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
use axum::{
|
||||
extract::{Json, Path},
|
||||
http::StatusCode,
|
||||
Extension,
|
||||
};
|
||||
use handlers::messages::update_message_handler;
|
||||
use middleware::AuthenticatedUser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::routes::rest::ApiResponse;
|
||||
|
||||
/// Request body for updating a message
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UpdateMessageRequest {
|
||||
/// Optional feedback for the message ("positive" or "negative")
|
||||
pub feedback: Option<String>,
|
||||
}
|
||||
|
||||
/// Update a specific message
|
||||
///
|
||||
/// This endpoint allows updating properties of a message, such as adding feedback.
|
||||
pub async fn update_message_rest_handler(
|
||||
Extension(user): Extension<AuthenticatedUser>,
|
||||
Path(message_id): Path<Uuid>,
|
||||
Json(request): Json<UpdateMessageRequest>,
|
||||
) -> Result<ApiResponse<()>, (StatusCode, &'static str)> {
|
||||
match update_message_handler(user, message_id, request.feedback).await {
|
||||
Ok(_) => Ok(ApiResponse::NoContent),
|
||||
Err(e) => {
|
||||
tracing::error!("Error updating message: {}", e);
|
||||
|
||||
let error_message = e.to_string();
|
||||
if error_message.contains("Message not found") {
|
||||
Err((StatusCode::NOT_FOUND, "Message not found"))
|
||||
} else if error_message.contains("don't have permission") {
|
||||
Err((StatusCode::FORBIDDEN, "You don't have permission to update this message"))
|
||||
} else if error_message.contains("must be either") {
|
||||
Err((StatusCode::BAD_REQUEST, "Feedback must be either 'positive' or 'negative'"))
|
||||
} else {
|
||||
Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to update message",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue