8cc1265
use export::users::UserDto;
use models::issue::issue_user;
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
QuerySelect, Set,
};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use utoipa::{IntoParams, ToSchema};
use uuid::Uuid;
use crate::errors::IssueError;
use crate::services::service::IssueService;
use crate::share::lookup;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct IssueUserDto {
pub id: Uuid,
pub user_id: i64,
pub user: Option<UserDto>,
pub issue_id: Uuid,
pub is_read: bool,
pub created_at: OffsetDateTime,
}
pub type IssueUserPage = export::pagination::Page<IssueUserDto>;
#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct UpdateIssueUserCmd {
#[serde(default)]
pub is_read: Option<bool>,
}
#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
pub struct ListIssueUserQuery {
pub limit: Option<u64>,
pub offset: Option<u64>,
}
#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
pub struct IssueUserPath {
pub space_name: String,
pub board_id: Uuid,
pub issue_id: String,
pub user_id: i64,
}
impl IssueService {
pub async fn list_issue_users(
&self,
caller: i64,
space_name: &str,
board_id: Uuid,
issue_id: Uuid,
q: ListIssueUserQuery,
) -> Result<IssueUserPage, IssueError> {
lookup::require_issue_access(self, caller, space_name, board_id, issue_id).await?;
let (limit, offset) = self.paginate(q.limit, q.offset);
let select = issue_user::Entity::find()
.filter(issue_user::Column::IssueId.eq(issue_id))
.order_by_desc(issue_user::Column::CreatedAt);
let total = select.clone().count(self.database.read()).await? as i64;
let rows = select
.limit(limit)
.offset(offset)
.all(self.database.read())
.await?;
let uids: Vec<i64> = rows.iter().map(|u| u.uid).collect();
let user_map = self.audience.load_user_briefs(&uids).await?;
let items = rows
.into_iter()
.map(|u| {
let user = user_map.get(&u.uid).cloned();
IssueUserDto {
id: u.id,
user_id: u.uid,
user,
issue_id: u.issue_id,
is_read: u.is_read,
created_at: u.created_at,
}
})
.collect();
Ok(IssueUserPage {
items,
total,
limit,
offset,
})
}
#[tracing::instrument(name = "issues.update_issue_user", skip_all, fields(caller = %caller, space = %space_name, board_id = %board_id, issue_id = %issue_id, uid = %uid))]
pub async fn update_issue_user(
&self,
caller: i64,
space_name: &str,
board_id: Uuid,
issue_id: Uuid,
uid: i64,
cmd: UpdateIssueUserCmd,
) -> Result<IssueUserDto, IssueError> {
lookup::require_issue_access(self, caller, space_name, board_id, issue_id).await?;
if caller != uid {
return Err(IssueError::Forbidden);
}
let existing = issue_user::Entity::find()
.filter(issue_user::Column::Uid.eq(uid))
.filter(issue_user::Column::IssueId.eq(issue_id))
.one(self.database.read())
.await?;
let now = OffsetDateTime::now_utc();
let updated = if let Some(row) = existing {
let mut am: issue_user::ActiveModel = row.into();
if let Some(r) = cmd.is_read {
am.is_read = Set(r);
}
am.update(self.database.write()).await?
} else {
let row = issue_user::ActiveModel {
id: Set(Uuid::now_v7()),
uid: Set(uid),
issue_id: Set(issue_id),
is_read: Set(cmd.is_read.unwrap_or(false)),
created_at: Set(now),
};
row.insert(self.database.write()).await?
};
let user_map = self.audience.load_user_briefs(&[uid]).await?;
let user = user_map.get(&uid).cloned();
Ok(IssueUserDto {
id: updated.id,
user_id: updated.uid,
user,
issue_id: updated.issue_id,
is_read: updated.is_read,
created_at: updated.created_at,
})
}
}