Files
imks/repo/message_create.rs
T
zhenyi 821537186e refactor(tests): reformat code and update dependency management
- Reorganized import statements in adapter tests for better readability
- Replaced or_insert_with(Vec::new) with or_default() in test closures
- Updated Cargo.lock with new dependency versions and checksums
- Added TLS features to tonic dependency configuration
- Included sqlx, chrono, and uuid dependencies with specific features
- Added jsonwebtoken and arc-swap as project dependencies
- Reformatted assertion statements to comply with line length limits
- Adjusted base64 import order in engine codec module
- Updated protobuf include statement formatting
2026-06-11 12:11:05 +08:00

117 lines
3.5 KiB
Rust

//! Message write operations — insert, update body, soft delete.
//!
//! All mutations use parameterized queries and return the affected row(s).
use chrono::Utc;
use uuid::Uuid;
use crate::ImksResult;
use crate::models::message::{Message, new_message_id};
use super::message_repo::MessageRepo;
/// Input payload for creating a new message.
#[derive(Debug, Clone)]
pub struct CreateMessageInput {
/// Target channel UUID.
pub channel_id: Uuid,
/// Author (user) UUID — extracted from JWT `sub` claim.
pub author_id: Uuid,
/// Thread this message belongs to (`None` = top-level).
pub thread_id: Option<Uuid>,
/// Direct reply reference (`None` = not a reply).
pub reply_to_message_id: Option<Uuid>,
/// Discriminator: `"text"`, `"system"`, `"event"`, `"article"`.
pub message_type: String,
/// Plain text or markdown body.
pub body: String,
/// Extensible metadata (flags, locale, etc.).
pub metadata: Option<serde_json::Value>,
/// Whether this is a system/bot-generated message.
pub system: bool,
}
impl MessageRepo {
/// Insert a new message row and return it.
///
/// The message ID is a fresh UUID v7 (time-ordered).
pub async fn create(&self, input: &CreateMessageInput) -> ImksResult<Message> {
let id = new_message_id();
let now = Utc::now();
let row = sqlx::query_as::<_, Message>(
r#"
INSERT INTO message (
id, channel_id, author_id, thread_id, reply_to_message_id,
message_type, body, metadata, pinned, system,
edited_at, deleted_at, created_at, updated_at
) VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8, FALSE, $9,
NULL, NULL, $10, $10
)
RETURNING *
"#,
)
.bind(id)
.bind(input.channel_id)
.bind(input.author_id)
.bind(input.thread_id)
.bind(input.reply_to_message_id)
.bind(&input.message_type)
.bind(&input.body)
.bind(&input.metadata)
.bind(input.system)
.bind(now)
.fetch_one(self.pool())
.await?;
Ok(row)
}
/// Update the body of an existing message. Sets `edited_at` and `updated_at`.
///
/// Returns the updated row, or an error if the message is not found or deleted.
pub async fn update_body(&self, message_id: Uuid, new_body: &str) -> ImksResult<Message> {
let now = Utc::now();
let row = sqlx::query_as::<_, Message>(
r#"
UPDATE message
SET body = $1, edited_at = $2, updated_at = $2
WHERE id = $3 AND deleted_at IS NULL
RETURNING *
"#,
)
.bind(new_body)
.bind(now)
.bind(message_id)
.fetch_optional(self.pool())
.await?
.ok_or_else(|| crate::ImksError::NotFound(format!("message {message_id}")))?;
Ok(row)
}
/// Soft-delete a message by setting `deleted_at`.
///
/// Returns `Ok(())` even if the message was already deleted.
pub async fn soft_delete(&self, message_id: Uuid) -> ImksResult<()> {
let now = Utc::now();
sqlx::query(
r#"
UPDATE message
SET deleted_at = $1, updated_at = $1
WHERE id = $2 AND deleted_at IS NULL
"#,
)
.bind(now)
.bind(message_id)
.execute(self.pool())
.await?;
Ok(())
}
}