gitdataai/libs/room/src/service/access_write.rs

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,
})
}
}