222 lines
5.9 KiB
Rust
222 lines
5.9 KiB
Rust
use cache::AppCache;
|
|
use db::{AppDatabase, sqlx};
|
|
use model::room::RoomMessageModel;
|
|
use serde::Serialize;
|
|
use uuid::Uuid;
|
|
|
|
use crate::{ChannelBusConfig, ChannelResult};
|
|
|
|
pub(crate) const RM_COLUMNS: &str =
|
|
"id, room, seq, thread, parent, author, content, content_type, pinned, \
|
|
system_type, metadata, edited_at, created_at, updated_at, deleted_at";
|
|
|
|
pub(crate) fn room_socket_name(room: Uuid) -> String {
|
|
format!("room:{room}")
|
|
}
|
|
|
|
pub(crate) fn user_rooms_cache_key(user: Uuid) -> String {
|
|
format!("channel:user:{user}:rooms")
|
|
}
|
|
#[derive(Debug, Serialize)]
|
|
pub struct RoomListItem {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub topic: Option<String>,
|
|
pub room_type: String,
|
|
pub is_private: bool,
|
|
pub category: Option<Uuid>,
|
|
pub workspace_id: Uuid,
|
|
}
|
|
#[derive(Debug, Serialize)]
|
|
pub struct CategoryListItem {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub position: i32,
|
|
}
|
|
pub async fn user_rooms_for_api(
|
|
db: &AppDatabase,
|
|
cache: &AppCache,
|
|
config: &ChannelBusConfig,
|
|
user: Uuid,
|
|
) -> ChannelResult<Vec<RoomListItem>> {
|
|
let room_ids = user_rooms(db, cache, config, user).await?;
|
|
if room_ids.is_empty() {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
let rows = sqlx::query_as::<_, (Uuid, String, Option<String>, String, bool, Option<Uuid>, Uuid)>(
|
|
"SELECT id, name, topic, room_type, is_private, parent, wk \
|
|
FROM room \
|
|
WHERE id = ANY($1) AND deleted_at IS NULL AND is_archived = false \
|
|
ORDER BY name",
|
|
)
|
|
.bind(&room_ids)
|
|
.fetch_all(db.reader())
|
|
.await?;
|
|
|
|
Ok(rows
|
|
.into_iter()
|
|
.map(|(id, name, topic, room_type, is_private, category, workspace_id)| RoomListItem {
|
|
id,
|
|
name,
|
|
topic,
|
|
room_type,
|
|
is_private,
|
|
category,
|
|
workspace_id,
|
|
})
|
|
.collect())
|
|
}
|
|
pub async fn user_categories_for_api(
|
|
db: &AppDatabase,
|
|
cache: &AppCache,
|
|
config: &ChannelBusConfig,
|
|
user: Uuid,
|
|
) -> ChannelResult<Vec<CategoryListItem>> {
|
|
let room_ids = user_rooms(db, cache, config, user).await?;
|
|
if room_ids.is_empty() {
|
|
return Ok(Vec::new());
|
|
}
|
|
let wk_rows = sqlx::query_as::<_, (Uuid,)>(
|
|
"SELECT DISTINCT wk FROM room WHERE id = ANY($1) AND deleted_at IS NULL",
|
|
)
|
|
.bind(&room_ids)
|
|
.fetch_all(db.reader())
|
|
.await?;
|
|
|
|
let wk_ids: Vec<Uuid> = wk_rows.into_iter().map(|r| r.0).collect();
|
|
if wk_ids.is_empty() {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
let rows = sqlx::query_as::<_, (Uuid, String, i32)>(
|
|
"SELECT id, name, position FROM room_category WHERE wk = ANY($1) ORDER BY position, name",
|
|
)
|
|
.bind(&wk_ids)
|
|
.fetch_all(db.reader())
|
|
.await?;
|
|
|
|
Ok(rows
|
|
.into_iter()
|
|
.map(|(id, name, position)| CategoryListItem { id, name, position })
|
|
.collect())
|
|
}
|
|
|
|
pub(crate) async fn user_rooms(
|
|
db: &AppDatabase,
|
|
cache: &AppCache,
|
|
config: &ChannelBusConfig,
|
|
user: Uuid,
|
|
) -> ChannelResult<Vec<Uuid>> {
|
|
let key = user_rooms_cache_key(user);
|
|
if let Some(rooms) = cache.get::<Vec<Uuid>>(&key).await? {
|
|
return Ok(rooms);
|
|
}
|
|
|
|
let rooms = load_user_rooms(db, user).await?;
|
|
cache_set_with_ttl(cache, &key, &rooms, config.room_cache_ttl_hint).await?;
|
|
Ok(rooms)
|
|
}
|
|
|
|
pub(crate) async fn refresh_user_rooms_cache(
|
|
db: &AppDatabase,
|
|
cache: &AppCache,
|
|
config: &ChannelBusConfig,
|
|
user: Uuid,
|
|
) -> ChannelResult<Vec<Uuid>> {
|
|
let key = user_rooms_cache_key(user);
|
|
cache.remove(&key).await?;
|
|
let rooms = load_user_rooms(db, user).await?;
|
|
cache_set_with_ttl(cache, &key, &rooms, config.room_cache_ttl_hint).await?;
|
|
Ok(rooms)
|
|
}
|
|
|
|
async fn cache_set_with_ttl<T>(
|
|
cache: &AppCache,
|
|
key: &str,
|
|
value: &T,
|
|
ttl: Option<std::time::Duration>,
|
|
) -> ChannelResult<()>
|
|
where
|
|
T: serde::Serialize + serde::de::DeserializeOwned,
|
|
{
|
|
cache.set(key, value).await?;
|
|
if let Some(ttl) = ttl {
|
|
if let Some(cluster) = &cache.cluster {
|
|
cluster.expire(key, ttl).await?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) async fn active_workspace_users(
|
|
db: &AppDatabase,
|
|
wk: Uuid,
|
|
) -> ChannelResult<Vec<Uuid>> {
|
|
let rows = sqlx::query_as::<_, (Uuid,)>(
|
|
"SELECT \"user\" FROM wk_member WHERE wk = $1 AND leave_at IS NULL",
|
|
)
|
|
.bind(wk)
|
|
.fetch_all(db.reader())
|
|
.await?;
|
|
Ok(rows.into_iter().map(|row| row.0).collect())
|
|
}
|
|
|
|
pub(crate) async fn room_workspace(
|
|
db: &AppDatabase,
|
|
room: Uuid,
|
|
) -> ChannelResult<Option<Uuid>> {
|
|
let row = sqlx::query_as::<_, (Uuid,)>("SELECT wk FROM room WHERE id = $1")
|
|
.bind(room)
|
|
.fetch_optional(db.reader())
|
|
.await?;
|
|
Ok(row.map(|row| row.0))
|
|
}
|
|
|
|
pub(crate) async fn catchup_messages(
|
|
db: &AppDatabase,
|
|
config: &ChannelBusConfig,
|
|
room: Uuid,
|
|
after_seq: i64,
|
|
) -> ChannelResult<Vec<RoomMessageModel>> {
|
|
let rows = sqlx::query_as::<_, RoomMessageModel>(
|
|
db::sqlx::AssertSqlSafe(format!(
|
|
"SELECT {RM_COLUMNS} FROM room_message \
|
|
WHERE room = $1 AND seq > $2 AND deleted_at IS NULL \
|
|
ORDER BY seq ASC \
|
|
LIMIT $3"
|
|
)),
|
|
)
|
|
.bind(room)
|
|
.bind(after_seq)
|
|
.bind(config.catchup_limit)
|
|
.fetch_all(db.reader())
|
|
.await?;
|
|
Ok(rows)
|
|
}
|
|
|
|
async fn load_user_rooms(
|
|
db: &AppDatabase,
|
|
user: Uuid,
|
|
) -> ChannelResult<Vec<Uuid>> {
|
|
let rows = sqlx::query_as::<_, (Uuid,)>(
|
|
"SELECT r.id \
|
|
FROM room r \
|
|
INNER JOIN wk_member wm ON wm.wk = r.wk \
|
|
WHERE wm.\"user\" = $1 \
|
|
AND wm.leave_at IS NULL \
|
|
AND r.deleted_at IS NULL \
|
|
AND r.is_archived = false \
|
|
AND (r.is_private = false \
|
|
OR EXISTS ( \
|
|
SELECT 1 FROM room_permission_overwrite po \
|
|
WHERE po.room = r.id AND po.target_id = $1 \
|
|
)) \
|
|
ORDER BY r.id",
|
|
)
|
|
.bind(user)
|
|
.fetch_all(db.reader())
|
|
.await?;
|
|
Ok(rows.into_iter().map(|row| row.0).collect())
|
|
}
|