use crate::AppService; use crate::error::AppError; use chrono::{DateTime, Utc}; use models::projects::{project_members, project_message_favorite}; use models::rooms::{room, room_message}; use sea_orm::sea_query::Expr; use sea_orm::*; use serde::{Deserialize, Serialize}; use session::Session; use utoipa::{IntoParams, ToSchema}; use uuid::Uuid; #[derive(Debug, Clone, Deserialize, Serialize, ToSchema, IntoParams)] pub struct ProjectMessageFavoriteQuery { pub page: Option, pub per_page: Option, } #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] pub struct ProjectMessageFavoriteItem { pub uid: Uuid, pub project_uid: Uuid, pub room_id: Uuid, pub room_name: String, pub message_id: Uuid, pub sender_id: Option, pub sender_type: String, pub display_name: Option, pub content: String, pub content_type: String, pub send_at: DateTime, pub favorited_at: DateTime, } #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] pub struct ProjectMessageFavoriteResponse { pub page: u64, pub per_page: u64, pub total: u64, pub list: Vec, } impl AppService { pub async fn project_message_favorites( &self, ctx: &Session, project_name: String, query: ProjectMessageFavoriteQuery, ) -> Result { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let project = self.utils_find_project_by_name(project_name).await?; self.require_project_member(project.id, user_uid).await?; let page = std::cmp::max(query.page.unwrap_or(1), 1); let per_page = std::cmp::min(std::cmp::max(query.per_page.unwrap_or(20), 1), 100); let base = project_message_favorite::Entity::find() .filter(project_message_favorite::Column::Project.eq(project.id)) .filter(project_message_favorite::Column::User.eq(user_uid)); let total = base.clone().count(&self.db).await?; let rows = base .join( JoinType::InnerJoin, project_message_favorite::Entity::belongs_to(room_message::Entity) .from(project_message_favorite::Column::Message) .to(room_message::Column::Id) .into(), ) .join( JoinType::InnerJoin, project_message_favorite::Entity::belongs_to(room::Entity) .from(project_message_favorite::Column::Room) .to(room::Column::Id) .into(), ) .filter(room_message::Column::Revoked.is_null()) .order_by_desc(project_message_favorite::Column::CreatedAt) .select_only() .column_as(project_message_favorite::Column::Uid, "uid") .column_as(project_message_favorite::Column::Project, "project_uid") .column_as(project_message_favorite::Column::Room, "room_id") .column_as(room::Column::RoomName, "room_name") .column_as(project_message_favorite::Column::Message, "message_id") .column_as(room_message::Column::SenderId, "sender_id") .column_as(Expr::cust("room_message.sender_type::text"), "sender_type") .column_as(room_message::Column::Content, "content") .column_as( Expr::cust("room_message.content_type::text"), "content_type", ) .column_as(room_message::Column::SendAt, "send_at") .column_as(project_message_favorite::Column::CreatedAt, "favorited_at") .column_as( Expr::cust( "coalesce(room_message.sender_id::text, room_message.sender_type::text)", ), "display_name", ) .limit(per_page) .offset((page - 1) * per_page) .into_tuple::<( Uuid, Uuid, Uuid, String, Uuid, Option, String, String, String, DateTime, DateTime, Option, )>() .all(&self.db) .await?; let list = rows .into_iter() .map( |( uid, project_uid, room_id, room_name, message_id, sender_id, sender_type, content, content_type, send_at, favorited_at, display_name, )| ProjectMessageFavoriteItem { uid, project_uid, room_id, room_name, message_id, sender_id, sender_type, display_name, content, content_type, send_at, favorited_at, }, ) .collect(); Ok(ProjectMessageFavoriteResponse { page, per_page, total, list, }) } pub async fn project_message_favorite_add( &self, ctx: &Session, project_name: String, message_id: Uuid, ) -> Result { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let project = self .utils_find_project_by_name(project_name.clone()) .await?; self.require_project_member(project.id, user_uid).await?; let message = room_message::Entity::find_by_id(message_id) .one(&self.db) .await? .ok_or_else(|| AppError::NotFound("message".to_string()))?; if message.revoked.is_some() { return Err(AppError::NotFound("message".to_string())); } let room = room::Entity::find_by_id(message.room) .one(&self.db) .await? .ok_or_else(|| AppError::NotFound("room".to_string()))?; if room.project != project.id { return Err(AppError::NotFound("message".to_string())); } if let Some(existing) = project_message_favorite::Entity::find() .filter(project_message_favorite::Column::User.eq(user_uid)) .filter(project_message_favorite::Column::Message.eq(message.id)) .one(&self.db) .await? { return Ok(ProjectMessageFavoriteItem { uid: existing.uid, project_uid: existing.project, room_id: existing.room, room_name: room.room_name, message_id: existing.message, sender_id: message.sender_id, sender_type: message.sender_type.to_string(), display_name: message .sender_id .map(|id| id.to_string()) .or_else(|| Some(message.sender_type.to_string())), content: message.content, content_type: message.content_type.to_string(), send_at: message.send_at, favorited_at: existing.created_at, }); } let created = project_message_favorite::ActiveModel { uid: Set(Uuid::new_v4()), project: Set(project.id), room: Set(message.room), message: Set(message.id), user: Set(user_uid), created_at: Set(Utc::now()), } .insert(&self.db) .await?; Ok(ProjectMessageFavoriteItem { uid: created.uid, project_uid: created.project, room_id: created.room, room_name: room.room_name, message_id: created.message, sender_id: message.sender_id, sender_type: message.sender_type.to_string(), display_name: message .sender_id .map(|id| id.to_string()) .or_else(|| Some(message.sender_type.to_string())), content: message.content, content_type: message.content_type.to_string(), send_at: message.send_at, favorited_at: created.created_at, }) } pub async fn project_message_favorite_remove( &self, ctx: &Session, project_name: String, message_id: Uuid, ) -> Result<(), AppError> { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let project = self.utils_find_project_by_name(project_name).await?; self.require_project_member(project.id, user_uid).await?; project_message_favorite::Entity::delete_many() .filter(project_message_favorite::Column::Project.eq(project.id)) .filter(project_message_favorite::Column::User.eq(user_uid)) .filter(project_message_favorite::Column::Message.eq(message_id)) .exec(&self.db) .await?; Ok(()) } async fn require_project_member( &self, project_id: Uuid, user_id: Uuid, ) -> Result<(), AppError> { let member = project_members::Entity::find() .filter(project_members::Column::Project.eq(project_id)) .filter(project_members::Column::User.eq(user_id)) .one(&self.db) .await?; if member.is_some() { Ok(()) } else { Err(AppError::NoPower) } } }