use models::users::user_reports::{self, ReportStatus}; use models::users::{user_recent_mentions, user_saved_messages}; use sea_orm::{ ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, Set, }; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use utoipa::ToSchema; use uuid::Uuid; use crate::errors::UserError; use crate::services::service::UserService; use crate::share::page::{Page, PageQuery}; #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct RecentMentionDto { pub id: Uuid, pub channel_id: Uuid, pub message_id: Uuid, pub created_at: OffsetDateTime, } impl From for RecentMentionDto { fn from(m: user_recent_mentions::Model) -> Self { Self { id: m.id, channel_id: m.channel_id, message_id: m.message_id, created_at: m.created_at, } } } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct UserReportDto { pub id: Uuid, pub reported_id: i64, pub reason: String, pub evidence: Option, pub status: ReportStatus, pub resolution_note: Option, pub created_at: OffsetDateTime, pub updated_at: OffsetDateTime, } impl From for UserReportDto { fn from(m: user_reports::Model) -> Self { Self { id: m.id, reported_id: m.reported_uid, reason: m.reason, evidence: m.evidence, status: m.status, resolution_note: m.resolution_note, created_at: m.created_at, updated_at: m.updated_at, } } } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct SavedMessageDto { pub id: Uuid, pub channel_id: Uuid, pub message_id: Uuid, pub saved_at: OffsetDateTime, } impl From for SavedMessageDto { fn from(m: user_saved_messages::Model) -> Self { Self { id: m.id, channel_id: m.channel_id, message_id: m.message_id, saved_at: m.saved_at, } } } #[derive(Debug, Clone, Deserialize, ToSchema)] pub struct SaveMessageCmd { pub channel_id: Uuid, pub message_id: Uuid, } impl UserService { pub async fn recent_mentions( &self, uid: i64, limit: Option, ) -> Result, UserError> { let lim = limit.unwrap_or(50).clamp(1, 50); let rows = user_recent_mentions::Entity::find() .filter(user_recent_mentions::Column::UserId.eq(uid)) .order_by_desc(user_recent_mentions::Column::CreatedAt) .limit(Some(lim)) .all(self.database.read()) .await?; Ok(rows.into_iter().map(RecentMentionDto::from).collect()) } pub async fn my_reports( &self, uid: i64, q: PageQuery, ) -> Result, UserError> { let (limit, offset) = q.bounds()?; let select = user_reports::Entity::find().filter(user_reports::Column::ReporterUid.eq(Some(uid))); let total = select.clone().count(self.database.read()).await?; let rows = select .order_by_desc(user_reports::Column::CreatedAt) .limit(limit) .offset(offset) .all(self.database.read()) .await?; let items = rows.into_iter().map(UserReportDto::from).collect(); Ok(Page::new(items, total, limit, offset)) } pub async fn list_saved_messages( &self, uid: i64, q: PageQuery, ) -> Result, UserError> { let (limit, offset) = q.bounds()?; let select = user_saved_messages::Entity::find().filter(user_saved_messages::Column::UserId.eq(uid)); let total = select.clone().count(self.database.read()).await?; let rows = select .order_by_desc(user_saved_messages::Column::SavedAt) .limit(limit) .offset(offset) .all(self.database.read()) .await?; let items = rows.into_iter().map(SavedMessageDto::from).collect(); Ok(Page::new(items, total, limit, offset)) } #[tracing::instrument(name = "user.save_message", skip_all, fields(uid = %uid))] pub async fn save_message( &self, uid: i64, cmd: SaveMessageCmd, ) -> Result { let existing = user_saved_messages::Entity::find() .filter(user_saved_messages::Column::UserId.eq(uid)) .filter(user_saved_messages::Column::ChannelId.eq(cmd.channel_id)) .filter(user_saved_messages::Column::MessageId.eq(cmd.message_id)) .one(self.database.read()) .await?; if let Some(row) = existing { return Ok(SavedMessageDto::from(row)); } let now = OffsetDateTime::now_utc(); let active = user_saved_messages::ActiveModel { id: Set(Uuid::now_v7()), user_id: Set(uid), channel_id: Set(cmd.channel_id), message_id: Set(cmd.message_id), saved_at: Set(now), }; let saved = active.insert(self.database.write()).await?; Ok(SavedMessageDto::from(saved)) } #[tracing::instrument( name = "user.unsave_message", skip_all, fields(uid = %uid, message_id = %message_id) )] pub async fn unsave_message(&self, uid: i64, message_id: Uuid) -> Result<(), UserError> { user_saved_messages::Entity::delete_many() .filter(user_saved_messages::Column::UserId.eq(uid)) .filter(user_saved_messages::Column::MessageId.eq(message_id)) .exec(self.database.write()) .await?; Ok(()) } }