use models::projects::{project, project_history_name, project_members}; use models::rooms::room; use sea_orm::*; use uuid::Uuid; use super::RoomService; use crate::error::RoomError; impl RoomService { pub(crate) fn validate_name(name: &str, max_len: usize) -> Result<(), RoomError> { if name.trim().is_empty() { return Err(RoomError::BadRequest("name cannot be empty".to_string())); } if name.len() > max_len { return Err(RoomError::BadRequest(format!( "name exceeds maximum length of {} characters", max_len ))); } Ok(()) } pub(crate) fn validate_content(content: &str, max_len: usize) -> Result<(), RoomError> { if content.trim().is_empty() { return Err(RoomError::BadRequest("content cannot be empty".to_string())); } if content.len() > max_len { return Err(RoomError::BadRequest(format!( "content exceeds maximum length of {} characters", max_len ))); } Ok(()) } pub(crate) fn sanitize_content(content: &str) -> String { ammonia::clean(content) } pub(crate) fn parse_message_content_type( content_type: Option, ) -> Result { match content_type.unwrap_or_else(|| "text".to_string()).to_lowercase().as_str() { "text" => Ok(models::rooms::MessageContentType::Text), "image" => Ok(models::rooms::MessageContentType::Image), "audio" => Ok(models::rooms::MessageContentType::Audio), "video" => Ok(models::rooms::MessageContentType::Video), "file" => Ok(models::rooms::MessageContentType::File), _ => Err(RoomError::BadRequest("invalid message content_type".to_string())), } } pub async fn utils_find_project_by_name(&self, name: String) -> Result { match project::Entity::find() .filter(project::Column::Name.eq(name.clone())) .one(&self.db) .await .inspect_err(|e| tracing::warn!(error = %e, project_name = %name, "utils_find_project_by_name: DB error")) .ok() .flatten() { Some(project) => Ok(project), None => match project_history_name::Entity::find() .filter(project_history_name::Column::HistoryName.eq(name.clone())) .one(&self.db) .await .inspect_err(|e| tracing::warn!(error = %e, name = %name, "project_history_name lookup failed")) .ok() .flatten() { Some(project) => self.utils_find_project_by_uid(project.project_uid).await, None => Err(RoomError::NotFound("Project not found".to_string())), }, } } pub async fn utils_find_project_by_uid(&self, uid: Uuid) -> Result { project::Entity::find_by_id(uid) .one(&self.db) .await .inspect_err(|e| tracing::warn!(error = %e, project_uid = %uid, "utils_find_project_by_uid: DB error")) .ok() .flatten() .ok_or_else(|| RoomError::NotFound("Project not found".to_string())) } pub async fn check_project_access(&self, project_uid: Uuid, user_uid: Uuid) -> Result<(), RoomError> { let project = project::Entity::find_by_id(project_uid) .one(&self.db) .await .inspect_err(|e| tracing::warn!(error = %e, project_uid = %project_uid, "check_project_access: DB error")) .ok() .flatten() .ok_or_else(|| RoomError::NotFound("Project not found".to_string()))?; if project.is_public { return Ok(()); } let member = project_members::Entity::find() .filter(project_members::Column::Project.eq(project_uid)) .filter(project_members::Column::User.eq(user_uid)) .one(&self.db) .await?; if member.is_some() { Ok(()) } else { Err(RoomError::NoPower) } } pub async fn ensure_room_visible_for_user( &self, room: &room::Model, user_id: Uuid, ) -> Result<(), RoomError> { self.require_room_access(room.id, user_id).await } pub async fn get_room_version(&self, room_id: Uuid) -> Result { let version_key = format!("room:version:{}", room_id); let mut conn = self.cache.conn().await.map_err(|e| { RoomError::Internal(format!("failed to get redis for version: {}", e)) })?; let version: Option = redis::cmd("GET") .arg(&version_key) .query_async(&mut conn) .await .map_err(|e| RoomError::Internal(format!("version GET: {}", e)))?; Ok(version.unwrap_or(0)) } pub async fn increment_room_version(&self, room_id: Uuid) -> Result { Self::raw_increment_room_version(&self.cache, room_id).await } pub async fn raw_increment_room_version( cache: &db::cache::AppCache, room_id: Uuid, ) -> Result { let version_key = format!("room:version:{}", room_id); let mut conn = cache.conn().await.map_err(|e| { RoomError::Internal(format!("failed to get redis for version: {}", e)) })?; let version: i64 = redis::cmd("INCR") .arg(&version_key) .query_async(&mut conn) .await .map_err(|e| RoomError::Internal(format!("version INCR: {}", e)))?; Ok(version) } }