use crate::AppService; use crate::error::AppError; use chrono::Utc; use models::projects::{ProjectWatch, project_audit_log, project_watch}; use models::users::user_email; use sea_orm::*; use serde::{Deserialize, Serialize}; use session::Session; use uuid::Uuid; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, utoipa::ToSchema)] pub struct WatchUserInfo { pub uid: Uuid, pub username: String, pub avatar_url: String, } impl AppService { pub async fn project_watch(&self, ctx: &Session, project_name: String) -> Result<(), AppError> { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let project = self.utils_find_project_by_name(project_name).await?; let watch_exists = ProjectWatch::find() .filter(project_watch::Column::User.eq(user_uid)) .filter(project_watch::Column::Project.eq(project.id)) .one(&self.db) .await?; if watch_exists.is_some() { return Err(AppError::BadRequest( "Already watching this project".to_string(), )); } ProjectWatch::insert(project_watch::ActiveModel { id: Default::default(), project: Set(project.id), user: Set(user_uid), notifications_enabled: Set(true), created_at: Set(Utc::now()), updated_at: Set(Utc::now()), }) .exec(&self.db) .await?; let _ = self .project_log_activity( project.id, None, user_uid, super::activity::ActivityLogParams { event_type: "project_watch".to_string(), title: format!("{} started watching the project", user_uid), repo_id: None, content: None, event_id: None, event_sub_id: None, metadata: Some(serde_json::json!({ "project_name": project.name.clone(), })), is_private: false, }, ) .await; let log = project_audit_log::ActiveModel { project: Set(project.id), actor: Set(user_uid), action: Set("project_watch".to_string()), details: Set(Some(serde_json::json!({ "project_name": project.name.clone(), }))), created_at: Set(Utc::now()), ..Default::default() }; log.insert(&self.db).await?; Ok(()) } pub async fn project_unwatch( &self, ctx: &Session, project_name: String, ) -> Result<(), AppError> { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let project = self.utils_find_project_by_name(project_name).await?; let watch_exists = ProjectWatch::find() .filter(project_watch::Column::User.eq(user_uid)) .filter(project_watch::Column::Project.eq(project.id)) .one(&self.db) .await?; if watch_exists.is_none() { return Err(AppError::NotFound("Not watching this project".to_string())); } ProjectWatch::delete_many() .filter(project_watch::Column::User.eq(user_uid)) .filter(project_watch::Column::Project.eq(project.id)) .exec(&self.db) .await?; let _ = self .project_log_activity( project.id, None, user_uid, super::activity::ActivityLogParams { event_type: "project_unwatch".to_string(), title: format!("{} stopped watching the project", user_uid), repo_id: None, content: None, event_id: None, event_sub_id: None, metadata: Some(serde_json::json!({ "project_name": project.name.clone(), })), is_private: false, }, ) .await; let log = project_audit_log::ActiveModel { project: Set(project.id), actor: Set(user_uid), action: Set("project_unwatch".to_string()), details: Set(Some(serde_json::json!({ "project_name": project.name.clone(), }))), created_at: Set(Utc::now()), ..Default::default() }; log.insert(&self.db).await?; Ok(()) } pub async fn project_is_watch( &self, ctx: &Session, project_name: String, ) -> Result { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let project = self.utils_find_project_by_name(project_name).await?; let watch_exists = ProjectWatch::find() .filter(project_watch::Column::User.eq(user_uid)) .filter(project_watch::Column::Project.eq(project.id)) .one(&self.db) .await?; Ok(watch_exists.is_some()) } pub async fn project_watches(&self, project_name: String) -> Result { let project = self.utils_find_project_by_name(project_name).await?; let watches = ProjectWatch::find() .filter(project_watch::Column::Project.eq(project.id)) .count(&self.db) .await?; Ok(watches) } pub async fn project_watch_user_list( &self, project_name: String, pager: crate::Pager, ) -> Result, AppError> { let project = self.utils_find_project_by_name(project_name).await?; let watches = ProjectWatch::find() .filter(project_watch::Column::Project.eq(project.id)) .order_by_desc(project_watch::Column::CreatedAt) .limit(pager.par_page as u64) .offset(((pager.page - 1) * pager.par_page) as u64) .all(&self.db) .await?; let user_uids: Vec = watches.into_iter().map(|watch| watch.user).collect(); if user_uids.is_empty() { return Ok(vec![]); } let users = models::users::user::Entity::find() .filter(models::users::user::Column::Uid.is_in(user_uids)) .all(&self.db) .await? .into_iter() .map(|u| WatchUserInfo { uid: u.uid, username: u.username, avatar_url: u.avatar_url.unwrap_or_default(), }) .collect(); Ok(users) } pub async fn project_watch_user_emails( &self, project_uid: Uuid, notify: bool, ) -> Result, AppError> { let watches = ProjectWatch::find() .filter(project_watch::Column::Project.eq(project_uid)) .all(&self.db) .await? .into_iter() .filter(|x| x.notifications_enabled == notify) .collect::>(); let user_uids: Vec = watches.into_iter().map(|watch| watch.user).collect(); if user_uids.is_empty() { return Ok(vec![]); } let emails = user_email::Entity::find() .filter(user_email::Column::User.is_in(user_uids)) .all(&self.db) .await? .into_iter() .map(|u| u.email) .collect(); Ok(emails) } }