Files
imks/repo/message_draft.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

114 lines
3.5 KiB
Rust

//! Draft CRUD operations on `MessageRepo`.
//!
//! One draft per (channel, user, thread). Upserted on every keystroke debounce,
//! deleted on send. Thread_id=NULL uses a dedicated conflict target.
use chrono::Utc;
use uuid::Uuid;
use crate::ImksResult;
use crate::models::message_draft::MessageDraft;
use super::message_repo::MessageRepo;
impl MessageRepo {
/// Upsert a draft for the given (channel, user, thread) key.
/// Uses NULL-safe conflict handling via COALESCE.
pub async fn upsert_draft(
&self,
channel_id: Uuid,
user_id: Uuid,
thread_id: Option<Uuid>,
body: &str,
reply_to_message_id: Option<Uuid>,
metadata: Option<serde_json::Value>,
) -> ImksResult<MessageDraft> {
let id = Uuid::now_v7();
let now = Utc::now();
let query = if thread_id.is_some() {
r#"
INSERT INTO message_draft (
id, channel_id, user_id, thread_id, reply_to_message_id,
body, metadata, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $8)
ON CONFLICT (channel_id, user_id, thread_id) DO UPDATE SET
body = EXCLUDED.body,
reply_to_message_id = EXCLUDED.reply_to_message_id,
metadata = EXCLUDED.metadata,
updated_at = EXCLUDED.updated_at
RETURNING *
"#
} else {
r#"
INSERT INTO message_draft (
id, channel_id, user_id, thread_id, reply_to_message_id,
body, metadata, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $8)
ON CONFLICT (channel_id, user_id) WHERE thread_id IS NULL DO UPDATE SET
body = EXCLUDED.body,
reply_to_message_id = EXCLUDED.reply_to_message_id,
metadata = EXCLUDED.metadata,
updated_at = EXCLUDED.updated_at
RETURNING *
"#
};
sqlx::query_as::<_, MessageDraft>(query)
.bind(id)
.bind(channel_id)
.bind(user_id)
.bind(thread_id)
.bind(reply_to_message_id)
.bind(body)
.bind(metadata)
.bind(now)
.fetch_one(self.pool())
.await
.map_err(Into::into)
}
/// Get a user's draft for a channel (optionally scoped to a thread).
pub async fn get_draft(
&self,
channel_id: Uuid,
user_id: Uuid,
thread_id: Option<Uuid>,
) -> ImksResult<Option<MessageDraft>> {
sqlx::query_as::<_, MessageDraft>(
r#"
SELECT * FROM message_draft
WHERE channel_id = $1 AND user_id = $2 AND thread_id IS NOT DISTINCT FROM $3
"#,
)
.bind(channel_id)
.bind(user_id)
.bind(thread_id)
.fetch_optional(self.pool())
.await
.map_err(Into::into)
}
/// Delete a draft after the message is sent.
pub async fn delete_draft(
&self,
channel_id: Uuid,
user_id: Uuid,
thread_id: Option<Uuid>,
) -> ImksResult<bool> {
let result = sqlx::query(
r#"
DELETE FROM message_draft
WHERE channel_id = $1 AND user_id = $2 AND thread_id IS NOT DISTINCT FROM $3
"#,
)
.bind(channel_id)
.bind(user_id)
.bind(thread_id)
.execute(self.pool())
.await?;
Ok(result.rows_affected() > 0)
}
}