340 lines
12 KiB
Rust
340 lines
12 KiB
Rust
use chrono::Utc;
|
|
use uuid::Uuid;
|
|
|
|
use crate::event::{RoomInfo, UserInfo, WorkspaceInfo, member, rooms};
|
|
use crate::{ChannelBus, ChannelError, ChannelResult};
|
|
|
|
use super::MAX_ROOM_NAME_LEN;
|
|
use super::WsHandler;
|
|
use super::WsOutEvent;
|
|
|
|
impl WsHandler {
|
|
pub(super) async fn room_get(
|
|
bus: &ChannelBus,
|
|
user_id: Uuid,
|
|
room: Uuid,
|
|
) -> ChannelResult<Option<WsOutEvent>> {
|
|
Self::ensure_room_access(bus, user_id, room).await?;
|
|
let row = db::sqlx::query_as::<_, model::room::RoomModel>(
|
|
"SELECT id, wk, parent, name, topic, room_type, position, \
|
|
is_private, is_archived, ai_enabled, created_by, created_at, updated_at, deleted_at \
|
|
FROM room WHERE id = $1 AND deleted_at IS NULL",
|
|
)
|
|
.bind(room)
|
|
.fetch_one(bus.inner.db.reader())
|
|
.await?;
|
|
Ok(Some(WsOutEvent::Response {
|
|
request_id: Uuid::nil(),
|
|
data: serde_json::json!({
|
|
"id": row.id,
|
|
"wk": row.wk,
|
|
"name": row.name,
|
|
"topic": row.topic,
|
|
"room_type": row.room_type,
|
|
"is_private": row.is_private,
|
|
"is_archived": row.is_archived,
|
|
"ai_enabled": row.ai_enabled,
|
|
"parent": row.parent,
|
|
"created_by": row.created_by,
|
|
"created_at": row.created_at,
|
|
}),
|
|
}))
|
|
}
|
|
|
|
pub(super) async fn room_create(
|
|
bus: &ChannelBus,
|
|
user_id: Uuid,
|
|
workspace: Uuid,
|
|
room_name: String,
|
|
public: bool,
|
|
category: Option<Uuid>,
|
|
ai_enabled: Option<bool>,
|
|
) -> ChannelResult<Option<WsOutEvent>> {
|
|
if room_name.is_empty() || room_name.len() > MAX_ROOM_NAME_LEN {
|
|
return Err(ChannelError::Validation("invalid room name".into()));
|
|
}
|
|
Self::ensure_workspace_member(bus, user_id, workspace).await?;
|
|
let is_private = !public;
|
|
let ai = ai_enabled.unwrap_or(false);
|
|
let row = db::sqlx::query_as::<_, model::room::RoomModel>(
|
|
"INSERT INTO room (wk, parent, name, room_type, is_private, ai_enabled, created_by, created_at, updated_at) \
|
|
VALUES ($1, $2, $3, 'channel', $4, $5, $6, now(), now()) \
|
|
RETURNING id, wk, parent, name, topic, room_type, position, \
|
|
is_private, is_archived, ai_enabled, created_by, created_at, updated_at, deleted_at",
|
|
)
|
|
.bind(workspace)
|
|
.bind(category)
|
|
.bind(&room_name)
|
|
.bind(is_private)
|
|
.bind(ai)
|
|
.bind(user_id)
|
|
.fetch_one(bus.inner.db.writer())
|
|
.await?;
|
|
db::sqlx::query(
|
|
"INSERT INTO room_permission_overwrite \
|
|
(room, target_type, target_id, allow_permissions, deny_permissions, created_at, updated_at) \
|
|
VALUES ($1, 'user', $2, '', '', now(), now())",
|
|
)
|
|
.bind(row.id)
|
|
.bind(user_id)
|
|
.execute(bus.inner.db.writer())
|
|
.await?;
|
|
let data = rooms::RoomCreatedService {
|
|
room: RoomInfo::from_model(&row),
|
|
workspace: bus
|
|
.lookup_workspace(workspace)
|
|
.await
|
|
.unwrap_or_else(|_| WorkspaceInfo::unknown(workspace)),
|
|
public,
|
|
category,
|
|
created_by: bus
|
|
.lookup_user(user_id)
|
|
.await
|
|
.unwrap_or_else(|_| UserInfo::unknown(user_id)),
|
|
created_at: row.created_at,
|
|
};
|
|
bus.publish_room_event(row.id, "room.created", &data)
|
|
.await?;
|
|
bus.room_changed(row.id).await?;
|
|
Ok(Some(WsOutEvent::RoomCreated {
|
|
room: data.room.clone(),
|
|
data,
|
|
}))
|
|
}
|
|
|
|
pub(super) async fn room_update(
|
|
bus: &ChannelBus,
|
|
user_id: Uuid,
|
|
room: Uuid,
|
|
room_name: Option<String>,
|
|
public: Option<bool>,
|
|
category: Option<Uuid>,
|
|
ai_enabled: Option<bool>,
|
|
) -> ChannelResult<Option<WsOutEvent>> {
|
|
Self::ensure_room_access(bus, user_id, room).await?;
|
|
let old = db::sqlx::query_as::<_, model::room::RoomModel>(
|
|
"SELECT id, wk, parent, name, topic, room_type, position, \
|
|
is_private, is_archived, ai_enabled, created_by, created_at, updated_at, deleted_at \
|
|
FROM room WHERE id = $1 AND deleted_at IS NULL",
|
|
)
|
|
.bind(room)
|
|
.fetch_one(bus.inner.db.reader())
|
|
.await?;
|
|
let new_name = room_name.unwrap_or(old.name.clone());
|
|
let new_private = public.map(|p| !p).unwrap_or(old.is_private);
|
|
let new_category = category.or(old.parent);
|
|
let new_ai = ai_enabled.unwrap_or(old.ai_enabled);
|
|
let row = db::sqlx::query_as::<_, model::room::RoomModel>(
|
|
"UPDATE room SET name = $2, is_private = $3, parent = $4, ai_enabled = $5, updated_at = now() \
|
|
WHERE id = $1 AND deleted_at IS NULL \
|
|
RETURNING id, wk, parent, name, topic, room_type, position, \
|
|
is_private, is_archived, ai_enabled, created_by, created_at, updated_at, deleted_at",
|
|
)
|
|
.bind(room)
|
|
.bind(&new_name)
|
|
.bind(new_private)
|
|
.bind(new_category)
|
|
.bind(new_ai)
|
|
.fetch_one(bus.inner.db.writer())
|
|
.await?;
|
|
let mut renamed = false;
|
|
if new_name != old.name {
|
|
let data = rooms::RoomRenamedService {
|
|
room: RoomInfo::from_model(&row),
|
|
workspace: bus
|
|
.lookup_workspace(row.wk)
|
|
.await
|
|
.unwrap_or_else(|_| WorkspaceInfo::unknown(row.wk)),
|
|
old_name: old.name.clone(),
|
|
new_name: new_name,
|
|
renamed_by: bus
|
|
.lookup_user(user_id)
|
|
.await
|
|
.unwrap_or_else(|_| UserInfo::unknown(user_id)),
|
|
renamed_at: Utc::now(),
|
|
};
|
|
bus.publish_room_event(room, "room.renamed", &data).await?;
|
|
renamed = true;
|
|
}
|
|
if new_private != old.is_private
|
|
|| new_category != old.parent
|
|
|| new_ai != old.ai_enabled
|
|
{
|
|
let data = rooms::RoomSettingsUpdatedService {
|
|
room: RoomInfo::from_model(&row),
|
|
workspace: bus
|
|
.lookup_workspace(row.wk)
|
|
.await
|
|
.unwrap_or_else(|_| WorkspaceInfo::unknown(row.wk)),
|
|
slowmode_seconds: None,
|
|
nsfw: false,
|
|
default_auto_archive_duration: None,
|
|
updated_by: bus
|
|
.lookup_user(user_id)
|
|
.await
|
|
.unwrap_or_else(|_| UserInfo::unknown(user_id)),
|
|
updated_at: Utc::now(),
|
|
};
|
|
bus.publish_room_event(room, "room.settings_updated", &data)
|
|
.await?;
|
|
}
|
|
bus.room_changed(room).await?;
|
|
if renamed {
|
|
return Ok(Some(WsOutEvent::RoomRenamed {
|
|
room: RoomInfo::from_model(&row),
|
|
data: rooms::RoomRenamedService {
|
|
room: RoomInfo::from_model(&row),
|
|
workspace: bus
|
|
.lookup_workspace(row.wk)
|
|
.await
|
|
.unwrap_or_else(|_| WorkspaceInfo::unknown(row.wk)),
|
|
old_name: old.name,
|
|
new_name: row.name,
|
|
renamed_by: bus
|
|
.lookup_user(user_id)
|
|
.await
|
|
.unwrap_or_else(|_| UserInfo::unknown(user_id)),
|
|
renamed_at: Utc::now(),
|
|
},
|
|
}));
|
|
}
|
|
Ok(Some(WsOutEvent::Response {
|
|
request_id: Uuid::nil(),
|
|
data: serde_json::json!({
|
|
"id": row.id,
|
|
"name": row.name,
|
|
"is_private": row.is_private,
|
|
"ai_enabled": row.ai_enabled,
|
|
"parent": row.parent,
|
|
}),
|
|
}))
|
|
}
|
|
|
|
pub(super) async fn room_delete(
|
|
bus: &ChannelBus,
|
|
user_id: Uuid,
|
|
room: Uuid,
|
|
) -> ChannelResult<Option<WsOutEvent>> {
|
|
Self::ensure_room_access(bus, user_id, room).await?;
|
|
let old = db::sqlx::query_as::<_, model::room::RoomModel>(
|
|
"SELECT id, wk, parent, name, topic, room_type, position, \
|
|
is_private, is_archived, created_by, created_at, updated_at, deleted_at \
|
|
FROM room WHERE id = $1 AND deleted_at IS NULL",
|
|
)
|
|
.bind(room)
|
|
.fetch_one(bus.inner.db.reader())
|
|
.await?;
|
|
if old.created_by != user_id {
|
|
return Err(ChannelError::AccessDenied);
|
|
}
|
|
let row = db::sqlx::query_as::<_, model::room::RoomModel>(
|
|
"UPDATE room SET deleted_at = now(), updated_at = now() \
|
|
WHERE id = $1 AND deleted_at IS NULL \
|
|
RETURNING id, wk, parent, name, topic, room_type, position, \
|
|
is_private, is_archived, created_by, created_at, updated_at, deleted_at",
|
|
)
|
|
.bind(room)
|
|
.fetch_one(bus.inner.db.writer())
|
|
.await?;
|
|
let data = rooms::RoomDeletedService {
|
|
room: RoomInfo::from_model(&row),
|
|
workspace: bus
|
|
.lookup_workspace(row.wk)
|
|
.await
|
|
.unwrap_or_else(|_| WorkspaceInfo::unknown(row.wk)),
|
|
deleted_by: bus
|
|
.lookup_user(user_id)
|
|
.await
|
|
.unwrap_or_else(|_| UserInfo::unknown(user_id)),
|
|
deleted_at: Utc::now(),
|
|
};
|
|
bus.publish_room_event(room, "room.deleted", &data).await?;
|
|
bus.room_changed(room).await?;
|
|
Ok(Some(WsOutEvent::RoomDeleted {
|
|
room: data.room.clone(),
|
|
data,
|
|
}))
|
|
}
|
|
|
|
pub(super) async fn access_grant(
|
|
bus: &ChannelBus,
|
|
user_id: Uuid,
|
|
room: Uuid,
|
|
target_user: Uuid,
|
|
) -> ChannelResult<Option<WsOutEvent>> {
|
|
Self::ensure_room_access(bus, user_id, room).await?;
|
|
db::sqlx::query(
|
|
"INSERT INTO room_permission_overwrite \
|
|
(room, target_type, target_id, allow_permissions, deny_permissions, created_at, updated_at) \
|
|
SELECT $1, 'user', $2, '', '', now(), now() \
|
|
WHERE NOT EXISTS (SELECT 1 FROM room_permission_overwrite WHERE room = $1 AND target_type = 'user' AND target_id = $2)",
|
|
)
|
|
.bind(room)
|
|
.bind(target_user)
|
|
.execute(bus.inner.db.writer())
|
|
.await?;
|
|
let mj_room = bus
|
|
.lookup_room(room)
|
|
.await
|
|
.unwrap_or_else(|_| RoomInfo::unknown(room));
|
|
let mj_user = bus
|
|
.lookup_user(target_user)
|
|
.await
|
|
.unwrap_or_else(|_| UserInfo::unknown(target_user));
|
|
let data = member::MemberJoinedService {
|
|
room: mj_room,
|
|
user: mj_user,
|
|
project_role: None,
|
|
joined_at: Utc::now(),
|
|
};
|
|
bus.publish_room_event(room, "member.joined", &data).await?;
|
|
bus.room_changed(room).await?;
|
|
Ok(Some(WsOutEvent::MemberJoined {
|
|
room: data.room.clone(),
|
|
data,
|
|
}))
|
|
}
|
|
|
|
pub(super) async fn access_revoke(
|
|
bus: &ChannelBus,
|
|
user_id: Uuid,
|
|
room: Uuid,
|
|
target_user: Uuid,
|
|
) -> ChannelResult<Option<WsOutEvent>> {
|
|
Self::ensure_room_access(bus, user_id, room).await?;
|
|
db::sqlx::query(
|
|
"DELETE FROM room_permission_overwrite \
|
|
WHERE room = $1 AND target_type = 'user' AND target_id = $2",
|
|
)
|
|
.bind(room)
|
|
.bind(target_user)
|
|
.execute(bus.inner.db.writer())
|
|
.await?;
|
|
let mr_room = bus
|
|
.lookup_room(room)
|
|
.await
|
|
.unwrap_or_else(|_| RoomInfo::unknown(room));
|
|
let mr_target = bus
|
|
.lookup_user(target_user)
|
|
.await
|
|
.unwrap_or_else(|_| UserInfo::unknown(target_user));
|
|
let mr_remover = bus
|
|
.lookup_user(user_id)
|
|
.await
|
|
.unwrap_or_else(|_| UserInfo::unknown(user_id));
|
|
let data = member::MemberRemovedService {
|
|
room: mr_room,
|
|
user: mr_target,
|
|
removed_by: mr_remover,
|
|
removed_at: Utc::now(),
|
|
};
|
|
bus.publish_room_event(room, "member.removed", &data)
|
|
.await?;
|
|
bus.room_changed(room).await?;
|
|
Ok(Some(WsOutEvent::MemberRemoved {
|
|
room: data.room.clone(),
|
|
data,
|
|
}))
|
|
}
|
|
}
|