Files
psg-backchannel/backchannel-server/src/db/messages.rs
2026-02-19 13:51:07 +08:00

211 lines
6.1 KiB
Rust

use backchannel_common::protocol::server::{HistoricChannelMessage, HistoricDm};
use chrono::{DateTime, Utc};
use sqlx::SqlitePool;
use uuid::Uuid;
use crate::error::{Result, ServerError};
#[derive(Debug, sqlx::FromRow)]
struct ChannelMessageRow {
pub id: String,
pub channel_id: String,
pub author_id: String,
pub author_username: String,
pub content: String,
pub created_at: String,
}
#[derive(Debug, sqlx::FromRow)]
struct DmRow {
pub id: String,
pub sender_id: String,
pub ciphertext: String,
pub nonce: String,
pub created_at: String,
}
pub async fn insert_channel_message(
pool: &SqlitePool,
channel_id: Uuid,
author_id: Uuid,
content: &str,
) -> Result<(Uuid, DateTime<Utc>)> {
let id = Uuid::new_v4();
let now = Utc::now();
let now_str = now.to_rfc3339();
sqlx::query(
"INSERT INTO channel_messages (id, channel_id, author_id, content, created_at)
VALUES (?, ?, ?, ?, ?)",
)
.bind(id.to_string())
.bind(channel_id.to_string())
.bind(author_id.to_string())
.bind(content)
.bind(&now_str)
.execute(pool)
.await?;
Ok((id, now))
}
pub async fn fetch_channel_history(
pool: &SqlitePool,
channel_id: Uuid,
before_message_id: Option<Uuid>,
limit: u32,
) -> Result<Vec<HistoricChannelMessage>> {
let limit = limit.min(100) as i64;
let rows: Vec<ChannelMessageRow> = if let Some(before_id) = before_message_id {
// Fetch messages older than `before_id` by joining to get its created_at
sqlx::query_as::<_, ChannelMessageRow>(
"SELECT m.id, m.channel_id, m.author_id, u.username AS author_username,
m.content, m.created_at
FROM channel_messages m
JOIN users u ON u.id = m.author_id
WHERE m.channel_id = ?
AND m.created_at < (SELECT created_at FROM channel_messages WHERE id = ?)
ORDER BY m.created_at DESC
LIMIT ?",
)
.bind(channel_id.to_string())
.bind(before_id.to_string())
.bind(limit)
.fetch_all(pool)
.await?
} else {
sqlx::query_as::<_, ChannelMessageRow>(
"SELECT m.id, m.channel_id, m.author_id, u.username AS author_username,
m.content, m.created_at
FROM channel_messages m
JOIN users u ON u.id = m.author_id
WHERE m.channel_id = ?
ORDER BY m.created_at DESC
LIMIT ?",
)
.bind(channel_id.to_string())
.bind(limit)
.fetch_all(pool)
.await?
};
let mut messages: Vec<HistoricChannelMessage> = rows
.into_iter()
.map(|r| {
let ts = r
.created_at
.parse::<DateTime<Utc>>()
.unwrap_or_else(|_| Utc::now());
Ok(HistoricChannelMessage {
message_id: Uuid::parse_str(&r.id)
.map_err(|e| ServerError::Internal(e.to_string()))?,
author_id: Uuid::parse_str(&r.author_id)
.map_err(|e| ServerError::Internal(e.to_string()))?,
author_username: r.author_username,
content: r.content,
timestamp: ts,
})
})
.collect::<Result<Vec<_>>>()?;
// Return in chronological order (oldest first).
messages.reverse();
Ok(messages)
}
pub async fn insert_dm(
pool: &SqlitePool,
sender_id: Uuid,
recipient_id: Uuid,
ciphertext: &str,
nonce: &str,
) -> Result<(Uuid, DateTime<Utc>)> {
let id = Uuid::new_v4();
let now = Utc::now();
let now_str = now.to_rfc3339();
sqlx::query(
"INSERT INTO direct_messages (id, sender_id, recipient_id, ciphertext, nonce, created_at)
VALUES (?, ?, ?, ?, ?, ?)",
)
.bind(id.to_string())
.bind(sender_id.to_string())
.bind(recipient_id.to_string())
.bind(ciphertext)
.bind(nonce)
.bind(&now_str)
.execute(pool)
.await?;
Ok((id, now))
}
pub async fn fetch_dm_history(
pool: &SqlitePool,
user_a: Uuid,
user_b: Uuid,
before_message_id: Option<Uuid>,
limit: u32,
) -> Result<Vec<HistoricDm>> {
let limit = limit.min(100) as i64;
let rows: Vec<DmRow> = if let Some(before_id) = before_message_id {
sqlx::query_as::<_, DmRow>(
"SELECT id, sender_id, ciphertext, nonce, created_at
FROM direct_messages
WHERE ((sender_id = ? AND recipient_id = ?)
OR (sender_id = ? AND recipient_id = ?))
AND created_at < (SELECT created_at FROM direct_messages WHERE id = ?)
ORDER BY created_at DESC
LIMIT ?",
)
.bind(user_a.to_string())
.bind(user_b.to_string())
.bind(user_b.to_string())
.bind(user_a.to_string())
.bind(before_id.to_string())
.bind(limit)
.fetch_all(pool)
.await?
} else {
sqlx::query_as::<_, DmRow>(
"SELECT id, sender_id, ciphertext, nonce, created_at
FROM direct_messages
WHERE (sender_id = ? AND recipient_id = ?)
OR (sender_id = ? AND recipient_id = ?)
ORDER BY created_at DESC
LIMIT ?",
)
.bind(user_a.to_string())
.bind(user_b.to_string())
.bind(user_b.to_string())
.bind(user_a.to_string())
.bind(limit)
.fetch_all(pool)
.await?
};
let mut messages: Vec<HistoricDm> = rows
.into_iter()
.map(|r| {
let ts = r
.created_at
.parse::<DateTime<Utc>>()
.unwrap_or_else(|_| Utc::now());
Ok(HistoricDm {
message_id: Uuid::parse_str(&r.id)
.map_err(|e| ServerError::Internal(e.to_string()))?,
sender_id: Uuid::parse_str(&r.sender_id)
.map_err(|e| ServerError::Internal(e.to_string()))?,
ciphertext: r.ciphertext,
nonce: r.nonce,
timestamp: ts,
})
})
.collect::<Result<Vec<_>>>()?;
messages.reverse();
Ok(messages)
}