restore/undo for deleted messages — backend + UI
- POST /api/channels/{id}/messages/{msg_id}/restore — undeletes a message
- Any user can restore (not just the deleter)
- Broadcasts restored message via WebSocket
- UI: "undo" button in floating pill on deleted messages
- API: restoreMessage() in api.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,10 @@ async fn main() {
|
||||
"/api/channels/{channel_id}/messages/{msg_id}",
|
||||
axum::routing::delete(routes::delete_message),
|
||||
)
|
||||
.route(
|
||||
"/api/channels/{channel_id}/messages/{msg_id}/restore",
|
||||
axum::routing::post(routes::restore_message),
|
||||
)
|
||||
.route("/ws/{channel_id}", get(ws::ws_handler))
|
||||
.fallback_service(
|
||||
ServeDir::new("static").fallback(ServeFile::new("static/index.html")),
|
||||
|
||||
@@ -319,6 +319,41 @@ pub async fn delete_message(
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
pub async fn restore_message(
|
||||
State(state): State<AppState>,
|
||||
Path((channel_id, msg_id)): Path<(String, String)>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
// Restore soft-deleted message (any user can restore)
|
||||
let result = sqlx::query(
|
||||
"UPDATE messages SET deleted_at = NULL WHERE id = ? AND channel_id = ? AND deleted_at IS NOT NULL",
|
||||
)
|
||||
.bind(&msg_id)
|
||||
.bind(&channel_id)
|
||||
.execute(&state.db)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
return Err(AppError::NotFound("Message not found or not deleted".into()));
|
||||
}
|
||||
|
||||
// Fetch restored message
|
||||
let row = sqlx::query_as::<_, MessageWithUserRow>(
|
||||
"SELECT m.*, u.id as u_id, u.username, u.display_name, u.role, u.created_at as u_created_at \
|
||||
FROM messages m JOIN users u ON m.user_id = u.id WHERE m.id = ?",
|
||||
)
|
||||
.bind(&msg_id)
|
||||
.fetch_one(&state.db)
|
||||
.await?;
|
||||
|
||||
let message = row.to_api_message();
|
||||
|
||||
// Broadcast as new message (restored)
|
||||
let tx = state.get_sender(&channel_id).await;
|
||||
let _ = tx.send(WsEvent::Message(message.clone()));
|
||||
|
||||
Ok(Json(message))
|
||||
}
|
||||
|
||||
// ── Joined row type for message + user ──
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
|
||||
Reference in New Issue
Block a user