use db::conn::sea_orm::{ ActiveModelTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, Set, }; use uuid::Uuid; use crate::error::{UserError, UserResult}; use crate::features::UserService; use super::types::{ ListNotificationsQuery, MarkReadCmd, NotificationView, RecentMentionView, ReportStatus, ReportView, SaveMessageCmd, SavedMessageView, ScheduledMessageStatus, ScheduledMessageView, SubmitReportCmd, UnreadCountResp, UpdateReportCmd, }; // === notifications === pub async fn list_notifications( svc: &UserService, caller: Uuid, q: ListNotificationsQuery, ) -> UserResult> { let db = svc.db.reader(); let limit = q.limit.min(svc.config.max_profile_pagination).max(1) as u64; let mut sel = db::user::user_notifications::Entity::find() .filter(db::user::user_notifications::COLUMN.user_id.eq(caller)) .order_by_desc(db::user::user_notifications::COLUMN.created_at) .limit(Some(limit)); if let Some(cursor) = q.cursor { sel = sel.filter(db::user::user_notifications::COLUMN.id.lt(cursor)); } if let Some(unread) = q.unread_only && unread { sel = sel.filter(db::user::user_notifications::COLUMN.read_at.is_null()); } if let Some(_nt) = q.notification_type { // Caller-side filter could be added; mapping to db enum omitted here. } let rows = sel.all(&db).await?; Ok(rows.into_iter().map(NotificationView::from).collect()) } pub async fn mark_read( svc: &UserService, caller: Uuid, cmd: MarkReadCmd, ) -> UserResult { if cmd.notification_ids.is_empty() { return Ok(0); } let writer = svc.db.writer(); let now = time::OffsetDateTime::now_utc(); let mut total = 0u64; for id in cmd.notification_ids { let row = db::user::user_notifications::Entity::find_by_id(id) .one(&writer) .await?; if let Some(m) = row { if m.user_id != caller || m.read_at.is_some() { continue; } let mut am: db::user::user_notifications::ActiveModel = m.into(); am.read_at = Set(Some(now)); am.update(&writer).await?; total += 1; } } Ok(total) } pub async fn mark_all_read(svc: &UserService, caller: Uuid) -> UserResult { let writer = svc.db.writer(); let rows = db::user::user_notifications::Entity::find() .filter(db::user::user_notifications::COLUMN.user_id.eq(caller)) .filter(db::user::user_notifications::COLUMN.read_at.is_null()) .all(&writer) .await?; let now = time::OffsetDateTime::now_utc(); let mut total = 0u64; for m in rows { let mut am: db::user::user_notifications::ActiveModel = m.into(); am.read_at = Set(Some(now)); am.update(&writer).await?; total += 1; } Ok(total) } pub async fn unread_count(svc: &UserService, caller: Uuid) -> UserResult { let db = svc.db.reader(); let count = db::user::user_notifications::Entity::find() .filter(db::user::user_notifications::COLUMN.user_id.eq(caller)) .filter(db::user::user_notifications::COLUMN.read_at.is_null()) .count(&db) .await?; Ok(UnreadCountResp { count: count as i64 }) } // === recent mentions === pub async fn list_recent_mentions( svc: &UserService, caller: Uuid, limit: usize, ) -> UserResult> { let limit = limit.min(svc.config.max_profile_pagination).max(1) as u64; let db = svc.db.reader(); let rows = db::user::user_recent_mentions::Entity::find() .filter(db::user::user_recent_mentions::COLUMN.user_id.eq(caller)) .order_by_desc(db::user::user_recent_mentions::COLUMN.created_at) .limit(Some(limit)) .all(&db) .await?; Ok(rows.into_iter().map(RecentMentionView::from).collect()) } // === reports === pub async fn submit_report( svc: &UserService, caller: Uuid, cmd: SubmitReportCmd, ) -> UserResult { if cmd.reason.is_empty() { return Err(UserError::BadRequest("reason required".into())); } let writer = svc.db.writer(); let now = time::OffsetDateTime::now_utc(); let active: db::user::user_reports::ActiveModel = db::user::user_reports::ActiveModel::builder() .set_id(Uuid::new_v4()) .set_reporter_uid(Some(caller)) .set_reported_uid(cmd.reported_uid) .set_reason(cmd.reason) .set_evidence(cmd.evidence) .set_status(ReportStatus::Open) .set_resolver_uid(None) .set_resolution_note(None) .set_created_at(now) .set_updated_at(now) .into(); let saved = active.insert(&writer).await?; Ok(ReportView::from(saved)) } pub async fn list_my_reports( svc: &UserService, caller: Uuid, ) -> UserResult> { let db = svc.db.reader(); let rows = db::user::user_reports::Entity::find() .filter(db::user::user_reports::COLUMN.reporter_uid.eq(caller)) .all(&db) .await?; Ok(rows.into_iter().map(ReportView::from).collect()) } pub async fn list_all_reports(svc: &UserService) -> UserResult> { let db = svc.db.reader(); let rows = db::user::user_reports::Entity::find().all(&db).await?; Ok(rows.into_iter().map(ReportView::from).collect()) } pub async fn update_report( svc: &UserService, report_id: Uuid, cmd: UpdateReportCmd, ) -> UserResult { let writer = svc.db.writer(); let row = db::user::user_reports::Entity::find_by_id(report_id) .one(&writer) .await? .ok_or_else(|| UserError::NotFound(format!("report {report_id}")))?; let mut am: db::user::user_reports::ActiveModel = row.into(); am.status = Set(cmd.status.into()); if cmd.resolution_note.is_some() { am.resolution_note = Set(cmd.resolution_note); } am.updated_at = Set(time::OffsetDateTime::now_utc()); let saved = am.update(&writer).await?; Ok(ReportView::from(saved)) } // === saved messages === pub async fn save_message( svc: &UserService, caller: Uuid, cmd: SaveMessageCmd, ) -> UserResult { let writer = svc.db.writer(); let existing = db::user::user_saved_messages::Entity::find() .filter(db::user::user_saved_messages::COLUMN.user_id.eq(caller)) .filter(db::user::user_saved_messages::COLUMN.channel_id.eq(cmd.channel_id)) .filter(db::user::user_saved_messages::COLUMN.message_id.eq(cmd.message_id)) .one(&writer) .await?; if let Some(row) = existing { return Ok(SavedMessageView::from(row)); } let now = time::OffsetDateTime::now_utc(); let active: db::user::user_saved_messages::ActiveModel = db::user::user_saved_messages::ActiveModel::builder() .set_id(Uuid::new_v4()) .set_user_id(caller) .set_channel_id(cmd.channel_id) .set_message_id(cmd.message_id) .set_saved_at(now) .into(); let saved = active.insert(&writer).await?; Ok(SavedMessageView::from(saved)) } pub async fn list_saved( svc: &UserService, caller: Uuid, ) -> UserResult> { let db = svc.db.reader(); let rows = db::user::user_saved_messages::Entity::find() .filter(db::user::user_saved_messages::COLUMN.user_id.eq(caller)) .order_by_desc(db::user::user_saved_messages::COLUMN.saved_at) .all(&db) .await?; Ok(rows.into_iter().map(SavedMessageView::from).collect()) } pub async fn unsave_message( svc: &UserService, caller: Uuid, message_id: Uuid, ) -> UserResult<()> { let writer = svc.db.writer(); db::user::user_saved_messages::Entity::delete_many() .filter(db::user::user_saved_messages::COLUMN.user_id.eq(caller)) .filter(db::user::user_saved_messages::COLUMN.message_id.eq(message_id)) .exec(&writer) .await?; Ok(()) } // === scheduled messages === pub async fn list_scheduled( svc: &UserService, caller: Uuid, ) -> UserResult> { let db = svc.db.reader(); let rows = db::user::user_scheduled_messages::Entity::find() .filter(db::user::user_scheduled_messages::COLUMN.user_id.eq(caller)) .all(&db) .await?; Ok(rows.into_iter().map(ScheduledMessageView::from).collect()) } pub async fn cancel_scheduled(svc: &UserService, caller: Uuid, id: Uuid) -> UserResult<()> { let writer = svc.db.writer(); let row = db::user::user_scheduled_messages::Entity::find_by_id(id) .one(&writer) .await? .ok_or_else(|| UserError::NotFound(format!("scheduled {id}")))?; if row.user_id != caller { return Err(UserError::Forbidden("not your scheduled message".into())); } let mut am: db::user::user_scheduled_messages::ActiveModel = row.into(); am.status = Set(ScheduledMessageStatus::Cancelled.into()); am.update(&writer).await?; Ok(()) }