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 updated_at: DateTime<Utc>,
|
||||||
pub deleted_at: Option<DateTime<Utc>>,
|
pub deleted_at: Option<DateTime<Utc>>,
|
||||||
pub created_by: Uuid,
|
pub created_by: Uuid,
|
||||||
|
pub feedback: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Insertable, Debug)]
|
#[derive(Queryable, Insertable, Debug)]
|
||||||
|
|
|
@ -334,6 +334,7 @@ diesel::table! {
|
||||||
updated_at -> Timestamptz,
|
updated_at -> Timestamptz,
|
||||||
deleted_at -> Nullable<Timestamptz>,
|
deleted_at -> Nullable<Timestamptz>,
|
||||||
created_by -> Uuid,
|
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),
|
final_reasoning_message: format!("Reasoned for {} seconds", reasoning_duration),
|
||||||
title: title.title.clone().unwrap_or_default(),
|
title: title.title.clone().unwrap_or_default(),
|
||||||
raw_llm_messages: serde_json::to_value(&raw_llm_messages)?,
|
raw_llm_messages: serde_json::to_value(&raw_llm_messages)?,
|
||||||
|
feedback: None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut conn = get_pg_pool().get().await?;
|
let mut conn = get_pg_pool().get().await?;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
pub mod delete_message_handler;
|
pub mod delete_message_handler;
|
||||||
|
pub mod update_message_handler;
|
||||||
|
|
||||||
pub use delete_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 serde_json::Value;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub mod message_feedback;
|
||||||
|
pub use message_feedback::MessageFeedback;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ChatMessage {
|
pub struct ChatMessage {
|
||||||
pub id: Uuid,
|
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::{
|
use axum::{
|
||||||
routing::delete,
|
routing::{delete, put},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod delete_message;
|
mod delete_message;
|
||||||
|
mod update_message;
|
||||||
|
|
||||||
use delete_message::delete_message_rest_handler;
|
use delete_message::delete_message_rest_handler;
|
||||||
|
use update_message::update_message_rest_handler;
|
||||||
|
|
||||||
/// Create a router for message-related endpoints
|
/// Create a router for message-related endpoints
|
||||||
pub fn router() -> Router {
|
pub fn router() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
// Delete a message and all subsequent messages in the same chat
|
// Delete a message and all subsequent messages in the same chat
|
||||||
.route("/:id", delete(delete_message_rest_handler))
|
.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