a06673b
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<Vec<NotificationView>> {
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<u64> {
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<u64> {
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<UnreadCountResp> {
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<Vec<RecentMentionView>> {
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<ReportView> {
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<Vec<ReportView>> {
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<Vec<ReportView>> {
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<ReportView> {
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<SavedMessageView> {
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<Vec<SavedMessageView>> {
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<Vec<ScheduledMessageView>> {
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(())
}