use crate::AppService; use crate::error::AppError; use chrono::Utc; use models::repos::repo_watch; use sea_orm::*; use serde::{Deserialize, Serialize}; use session::Session; #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct WatchCountResponse { pub count: i64, } #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct WatchUserInfo { pub uid: String, pub username: String, pub avatar_url: String, } #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct WatchUserListResponse { pub users: Vec, } #[derive(Debug, Clone, Deserialize, utoipa::ToSchema)] pub struct GitWatchRequest { #[serde(default = "default_show_dashboard")] pub show_dashboard: bool, #[serde(default)] pub notify_email: bool, } fn default_show_dashboard() -> bool { true } use models::repos::RepoWatch; use models::repos::repo as repo_model; use uuid::Uuid; impl AppService { pub async fn git_watch( &self, namespace: String, repo_name: String, request: GitWatchRequest, ctx: &Session, ) -> Result<(), AppError> { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let repo = self .utils_find_repo(namespace, repo_name.clone(), ctx) .await?; let existing: Option = RepoWatch::find() .filter(repo_watch::Column::User.eq(user_uid)) .filter(repo_watch::Column::Repo.eq(repo.id)) .one(&self.db) .await?; if existing.is_some() { return Err(AppError::InternalServerError( "already watching".to_string(), )); } RepoWatch::insert(repo_watch::ActiveModel { id: Default::default(), user: Set(user_uid), repo: Set(repo.id), show_dashboard: Set(request.show_dashboard), notify_email: Set(request.notify_email), created_at: Set(Utc::now()), updated_at: Set(Utc::now()), }) .exec(&self.db) .await?; let project_id = match repo_model::Entity::find_by_id(repo.id).one(&self.db).await { Ok(Some(r)) => r.project, Ok(None) => Uuid::nil(), Err(e) => { slog::warn!( self.logs, "failed to look up project_id for activity log: {}", e ); Uuid::nil() } }; let _ = self .project_log_activity( project_id, Some(repo.id), user_uid, super::super::project::activity::ActivityLogParams { event_type: "repo_watch".to_string(), title: format!("{} started watching repository '{}'", user_uid, repo_name), repo_id: Some(repo.id), content: None, event_id: None, event_sub_id: None, metadata: Some(serde_json::json!({"repo_name": repo_name})), is_private: false, }, ) .await; Ok(()) } pub async fn git_unwatch( &self, namespace: String, repo_name: String, ctx: &Session, ) -> Result<(), AppError> { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let repo = self .utils_find_repo(namespace, repo_name.clone(), ctx) .await?; let deleted = RepoWatch::delete_many() .filter(repo_watch::Column::User.eq(user_uid)) .filter(repo_watch::Column::Repo.eq(repo.id)) .exec(&self.db) .await?; if deleted.rows_affected == 0 { return Err(AppError::InternalServerError("not watching".to_string())); } let project_id = match repo_model::Entity::find_by_id(repo.id).one(&self.db).await { Ok(Some(r)) => r.project, Ok(None) => Uuid::nil(), Err(e) => { slog::warn!( self.logs, "failed to look up project_id for activity log: {}", e ); Uuid::nil() } }; let _ = self .project_log_activity( project_id, Some(repo.id), user_uid, super::super::project::activity::ActivityLogParams { event_type: "repo_unwatch".to_string(), title: format!("{} stopped watching repository '{}'", user_uid, repo_name), repo_id: Some(repo.id), content: None, event_id: None, event_sub_id: None, metadata: Some(serde_json::json!({"repo_name": repo_name})), is_private: false, }, ) .await; Ok(()) } pub async fn git_is_watched( &self, namespace: String, repo_name: String, ctx: &Session, ) -> Result { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let existing: Option = RepoWatch::find() .filter(repo_watch::Column::User.eq(user_uid)) .filter(repo_watch::Column::Repo.eq(repo.id)) .one(&self.db) .await?; Ok(existing.is_some()) } pub async fn git_watch_count( &self, namespace: String, repo_name: String, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let count = RepoWatch::find() .filter(repo_watch::Column::Repo.eq(repo.id)) .count(&self.db) .await?; Ok(WatchCountResponse { count: count as i64, }) } pub async fn git_watch_user_list( &self, namespace: String, repo_name: String, pager: crate::Pager, ctx: &Session, ) -> Result { let repo = self.utils_find_repo(namespace, repo_name, ctx).await?; let page = std::cmp::Ord::max(pager.page, 1); let par_page = std::cmp::Ord::min(std::cmp::Ord::max(pager.par_page, 1), 1000); let offset_val = (page - 1).saturating_mul(par_page); let offset = offset_val as u64; let watches: Vec = RepoWatch::find() .filter(repo_watch::Column::Repo.eq(repo.id)) .order_by_desc(repo_watch::Column::CreatedAt) .limit(par_page as u64) .offset(offset) .all(&self.db) .await?; let user_uids: Vec = watches.into_iter().map(|w| w.user).collect(); if user_uids.is_empty() { return Ok(WatchUserListResponse { users: 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.to_string(), username: u.username, avatar_url: u.avatar_url.unwrap_or_default(), }) .collect(); Ok(WatchUserListResponse { users }) } }