use api::users::UserDto; use models::common::watch_mode::WatchMode; use models::issue::issue_watch; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, 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 WatchDto { pub id: Uuid, pub issue_id: Option, pub user: Option, pub mode: WatchMode, pub created_at: OffsetDateTime, pub updated_at: Option, } #[derive(Debug, Clone, Deserialize, ToSchema)] pub struct UpsertWatchCmd { pub mode: WatchMode, } #[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)] pub struct WatchPath { pub space_name: String, pub board_id: Uuid, pub issue_id: String, } impl IssueService { #[tracing::instrument(name = "issues.upsert_watch", skip_all, fields(caller = %caller, space = %space_name, board_id = %board_id, issue_id = %issue_id))] pub async fn upsert_watch( &self, caller: i64, space_name: &str, board_id: Uuid, issue_id: Uuid, cmd: UpsertWatchCmd, ) -> Result { lookup::require_issue_access(self, caller, space_name, board_id, issue_id).await?; let existing = issue_watch::Entity::find() .filter(issue_watch::Column::IssueId.eq(Some(issue_id))) .filter(issue_watch::Column::UserId.eq(caller)) .one(self.database.read()) .await?; let now = OffsetDateTime::now_utc(); let (id, created_at, updated_at) = if let Some(w) = existing { let mut am: issue_watch::ActiveModel = w.into(); am.mode = Set(cmd.mode); am.updated_at = Set(Some(now)); let u = am.update(self.database.write()).await?; (u.id, u.created_at, u.updated_at) } else { let row = issue_watch::ActiveModel { id: Set(Uuid::now_v7()), issue_id: Set(Some(issue_id)), project_id: Set(None), user_id: Set(caller), mode: Set(cmd.mode), created_at: Set(now), updated_at: Set(None), }; let ins = row.insert(self.database.write()).await?; (ins.id, ins.created_at, ins.updated_at) }; let user_map = self.audience.load_user_briefs(&[caller]).await?; let user = user_map.get(&caller).cloned(); Ok(WatchDto { id, issue_id: Some(issue_id), user, mode: cmd.mode, created_at, updated_at, }) } #[tracing::instrument(name = "issues.delete_watch", skip_all, fields(caller = %caller, space = %space_name, board_id = %board_id, issue_id = %issue_id))] pub async fn delete_watch( &self, caller: i64, space_name: &str, board_id: Uuid, issue_id: Uuid, ) -> Result<(), IssueError> { lookup::require_issue_access(self, caller, space_name, board_id, issue_id).await?; let res = issue_watch::Entity::delete_many() .filter(issue_watch::Column::IssueId.eq(Some(issue_id))) .filter(issue_watch::Column::UserId.eq(caller)) .exec(self.database.write()) .await?; if res.rows_affected == 0 { return Err(IssueError::NotFound); } Ok(()) } }