use crate::AppService; use crate::error::AppError; use chrono::Utc; use models::repos::repo as repo_model; use models::repos::{RepoStar, repo_star}; use sea_orm::*; use serde::{Deserialize, Serialize}; use session::Session; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct StarCountResponse { pub count: i64, } #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct StarUserInfo { pub uid: String, pub username: String, pub avatar_url: String, } #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct StarUserListResponse { pub users: Vec, } impl AppService { pub async fn git_star( &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 existing = RepoStar::find() .filter(repo_star::Column::User.eq(user_uid)) .filter(repo_star::Column::Repo.eq(repo.id)) .one(&self.db) .await?; if existing.is_some() { return Err(AppError::InternalServerError("already starred".to_string())); } RepoStar::insert(repo_star::ActiveModel { id: Default::default(), repo: Set(repo.id), user: Set(user_uid), created_at: Set(Utc::now()), }) .exec(&self.db) .await?; // Log activity: need to look up project_id from repo 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_star".to_string(), title: format!("{} starred 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_unstar( &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 = RepoStar::delete_many() .filter(repo_star::Column::User.eq(user_uid)) .filter(repo_star::Column::Repo.eq(repo.id)) .exec(&self.db) .await?; if deleted.rows_affected == 0 { return Err(AppError::InternalServerError("not starred".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_unstar".to_string(), title: format!("{} unstarred 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_starred( &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.clone(), ctx) .await?; let existing = RepoStar::find() .filter(repo_star::Column::User.eq(user_uid)) .filter(repo_star::Column::Repo.eq(repo.id)) .one(&self.db) .await?; Ok(existing.is_some()) } pub async fn git_star_count( &self, namespace: String, repo_name: String, ctx: &Session, ) -> Result { let repo = self .utils_find_repo(namespace, repo_name.clone(), ctx) .await?; let count = RepoStar::find() .filter(repo_star::Column::Repo.eq(repo.id)) .count(&self.db) .await?; Ok(StarCountResponse { count: count as i64, }) } pub async fn git_star_user_list( &self, namespace: String, repo_name: String, pager: crate::Pager, ctx: &Session, ) -> Result { let repo = self .utils_find_repo(namespace, repo_name.clone(), 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 stars = RepoStar::find() .filter(repo_star::Column::Repo.eq(repo.id)) .order_by_desc(repo_star::Column::CreatedAt) .limit(par_page as u64) .offset(offset) .all(&self.db) .await?; let user_uids: Vec = stars.into_iter().map(|s| s.user).collect(); if user_uids.is_empty() { return Ok(StarUserListResponse { 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| StarUserInfo { uid: u.uid.to_string(), username: u.username, avatar_url: u.avatar_url.unwrap_or_default(), }) .collect(); Ok(StarUserListResponse { users }) } }