186 lines
5.7 KiB
Rust
186 lines
5.7 KiB
Rust
use crate::RoomEventType;
|
|
use crate::RoomUserStateResponse;
|
|
use crate::RoomUserStateUpdateDndRequest;
|
|
use crate::error::RoomError;
|
|
use crate::service::RoomService;
|
|
use crate::ws_context::WsUserContext;
|
|
use chrono::Utc;
|
|
use db::database::AppDatabase;
|
|
use models::rooms::{room_access, room_user_state};
|
|
use sea_orm::*;
|
|
use uuid::Uuid;
|
|
|
|
/// Grant explicit access to a private room.
|
|
/// For public rooms this is a no-op (project membership suffices).
|
|
pub async fn grant_room_access(
|
|
db: &AppDatabase,
|
|
room_id: Uuid,
|
|
target_user_id: Uuid,
|
|
granted_by: Uuid,
|
|
) -> Result<(), RoomError> {
|
|
let existing = room_access::Entity::find_by_id((room_id, target_user_id))
|
|
.one(db)
|
|
.await?;
|
|
if existing.is_some() {
|
|
return Ok(());
|
|
}
|
|
|
|
let _created = room_access::ActiveModel {
|
|
room: Set(room_id),
|
|
user: Set(target_user_id),
|
|
granted_by: Set(granted_by),
|
|
granted_at: Set(Utc::now()),
|
|
}
|
|
.insert(db)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Revoke explicit access from a private room.
|
|
pub async fn revoke_room_access(
|
|
db: &AppDatabase,
|
|
room_id: Uuid,
|
|
target_user_id: Uuid,
|
|
) -> Result<(), RoomError> {
|
|
room_access::Entity::delete_by_id((room_id, target_user_id))
|
|
.exec(db)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
impl RoomService {
|
|
/// Grant access to a private room (creates room_access row).
|
|
/// Public rooms don't need explicit access — this is a no-op.
|
|
pub async fn room_access_grant(
|
|
&self,
|
|
room_id: Uuid,
|
|
target_user_id: Uuid,
|
|
ctx: &WsUserContext,
|
|
) -> Result<(), RoomError> {
|
|
let room = self.find_room_or_404(room_id).await?;
|
|
self.require_room_admin(room_id, ctx.user_id).await?;
|
|
|
|
if room.public {
|
|
// Public rooms don't need explicit access grants
|
|
return Err(RoomError::BadRequest(
|
|
"public rooms do not need explicit access grants".to_string(),
|
|
));
|
|
}
|
|
|
|
grant_room_access(&self.db, room_id, target_user_id, ctx.user_id).await?;
|
|
|
|
// Ensure user state exists (for last_read_seq tracking etc.)
|
|
let _ = crate::service::access::get_or_create_room_user_state(
|
|
&self.db,
|
|
room_id,
|
|
target_user_id,
|
|
)
|
|
.await;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Revoke access from a private room.
|
|
pub async fn room_access_revoke(
|
|
&self,
|
|
room_id: Uuid,
|
|
target_user_id: Uuid,
|
|
ctx: &WsUserContext,
|
|
) -> Result<(), RoomError> {
|
|
self.require_room_admin(room_id, ctx.user_id).await?;
|
|
|
|
if target_user_id == ctx.user_id {
|
|
return Err(RoomError::BadRequest(
|
|
"cannot revoke your own access".to_string(),
|
|
));
|
|
}
|
|
|
|
revoke_room_access(&self.db, room_id, target_user_id).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Update read position (last_read_seq) for a room.
|
|
pub async fn room_user_state_update_read_seq(
|
|
&self,
|
|
room_id: Uuid,
|
|
last_read_seq: i64,
|
|
ctx: &WsUserContext,
|
|
) -> Result<RoomUserStateResponse, RoomError> {
|
|
self.require_room_access(room_id, ctx.user_id).await?;
|
|
|
|
let state =
|
|
crate::service::access::get_or_create_room_user_state(&self.db, room_id, ctx.user_id)
|
|
.await?;
|
|
|
|
let mut active: room_user_state::ActiveModel = state.into();
|
|
active.last_read_seq = Set(Some(last_read_seq));
|
|
let updated = active.update(&self.db).await?;
|
|
|
|
let room = self.find_room_or_404(room_id).await?;
|
|
self.invalidate_room_list_cache_for_user(room.project, ctx.user_id)
|
|
.await;
|
|
self.publish_room_event(
|
|
room.project,
|
|
RoomEventType::ReadReceipt,
|
|
Some(room_id),
|
|
None,
|
|
Some(ctx.user_id),
|
|
Some(last_read_seq),
|
|
)
|
|
.await;
|
|
|
|
Ok(RoomUserStateResponse {
|
|
room: updated.room,
|
|
user: updated.user,
|
|
last_read_seq: updated.last_read_seq,
|
|
do_not_disturb: updated.do_not_disturb,
|
|
dnd_start_hour: updated.dnd_start_hour,
|
|
dnd_end_hour: updated.dnd_end_hour,
|
|
joined_at: updated.joined_at,
|
|
})
|
|
}
|
|
|
|
/// Update DND settings for a room.
|
|
pub async fn room_user_state_update_dnd(
|
|
&self,
|
|
room_id: Uuid,
|
|
request: RoomUserStateUpdateDndRequest,
|
|
ctx: &WsUserContext,
|
|
) -> Result<RoomUserStateResponse, RoomError> {
|
|
self.require_room_access(room_id, ctx.user_id).await?;
|
|
|
|
let state =
|
|
crate::service::access::get_or_create_room_user_state(&self.db, room_id, ctx.user_id)
|
|
.await?;
|
|
|
|
let mut active: room_user_state::ActiveModel = state.into();
|
|
if let Some(dnd) = request.do_not_disturb {
|
|
active.do_not_disturb = Set(dnd);
|
|
}
|
|
if let Some(start) = request.dnd_start_hour {
|
|
if !(0..=23).contains(&start) {
|
|
return Err(RoomError::BadRequest("dnd_start_hour must be 0-23".into()));
|
|
}
|
|
active.dnd_start_hour = Set(Some(start));
|
|
}
|
|
if let Some(end) = request.dnd_end_hour {
|
|
if !(0..=23).contains(&end) {
|
|
return Err(RoomError::BadRequest("dnd_end_hour must be 0-23".into()));
|
|
}
|
|
active.dnd_end_hour = Set(Some(end));
|
|
}
|
|
let updated = active.update(&self.db).await?;
|
|
|
|
Ok(RoomUserStateResponse {
|
|
room: updated.room,
|
|
user: updated.user,
|
|
last_read_seq: updated.last_read_seq,
|
|
do_not_disturb: updated.do_not_disturb,
|
|
dnd_start_hour: updated.dnd_start_hour,
|
|
dnd_end_hour: updated.dnd_end_hour,
|
|
joined_at: updated.joined_at,
|
|
})
|
|
}
|
|
}
|