use db::conn::sea_orm::*; use db::repo::repo_collaborations as collab_entity; use db::repo::repo_collaborations::CollaborationMode; use time::OffsetDateTime; use uuid::Uuid; use crate::error::{RepoError, RepoResult}; use crate::features::RepoService; use super::types::{ AddCollaboratorOption, CollaboratorPage, CollaboratorView, RepoCollaboratorPermission, UserSummary, }; fn parse_mode(s: &str) -> RepoResult { match s { "read" | "Read" | "READ" => Ok(CollaborationMode::Read), "write" | "Write" | "WRITE" => Ok(CollaborationMode::Write), "admin" | "Admin" | "ADMIN" => Ok(CollaborationMode::Admin), _ => Err(RepoError::BadRequest(format!("invalid permission: {s}"))), } } fn mode_to_str(m: CollaborationMode) -> String { match m { CollaborationMode::Read => "read".to_string(), CollaborationMode::Write => "write".to_string(), CollaborationMode::Admin => "admin".to_string(), } } fn require_admin(actor: Uuid) -> RepoResult<()> { if actor.is_nil() { return Err(RepoError::Unauthorized("authentication required".into())); } Ok(()) } pub async fn list_collaborators( svc: &RepoService, actor: Uuid, repo_id: Uuid, page: Option, limit: Option, ) -> RepoResult { let _ = actor; let limit = limit.unwrap_or(svc.config.default_pagination); let offset = page.unwrap_or(0).saturating_mul(limit); let limit = std::cmp::min(limit, svc.config.max_pagination); let db = svc.db.reader(); let query = collab_entity::Entity::find().filter(collab_entity::Column::RepoId.eq(repo_id)); let total = query.clone().count(&db).await?; let items = query .limit(Some(limit)) .offset(Some(offset)) .all(&db) .await? .into_iter() .map(CollaboratorView::from) .collect(); Ok(CollaboratorPage { items, total: total as i64, }) } pub async fn is_collaborator( svc: &RepoService, actor: Uuid, repo_id: Uuid, user_id: Uuid, ) -> RepoResult { let _ = actor; let db = svc.db.reader(); let hit = collab_entity::Entity::find() .filter(collab_entity::Column::RepoId.eq(repo_id)) .filter(collab_entity::Column::UserId.eq(user_id)) .one(&db) .await?; Ok(hit.is_some()) } pub async fn add_collaborator( svc: &RepoService, actor: Uuid, repo_id: Uuid, user_id: Uuid, input: AddCollaboratorOption, ) -> RepoResult<()> { require_admin(actor)?; let mode = parse_mode(&input.permission)?; let db = svc.db.writer(); let now = OffsetDateTime::now_utc(); let existing = collab_entity::Entity::find() .filter(collab_entity::Column::RepoId.eq(repo_id)) .filter(collab_entity::Column::UserId.eq(user_id)) .one(&db) .await?; if let Some(current) = existing { let mut active: collab_entity::ActiveModel = current.into(); active.mode = Set(mode); active.updated_at = Set(now); active.update(&db).await?; } else { let active: collab_entity::ActiveModel = collab_entity::ActiveModel { id: Set(Uuid::new_v4()), repo_id: Set(repo_id), user_id: Set(user_id), mode: Set(mode), created_at: Set(now), updated_at: Set(now), }; active.insert(&db).await?; } Ok(()) } pub async fn delete_collaborator( svc: &RepoService, actor: Uuid, repo_id: Uuid, user_id: Uuid, ) -> RepoResult<()> { require_admin(actor)?; let db = svc.db.writer(); collab_entity::Entity::delete_many() .filter(collab_entity::Column::RepoId.eq(repo_id)) .filter(collab_entity::Column::UserId.eq(user_id)) .exec(&db) .await?; Ok(()) } pub async fn get_repo_permissions( svc: &RepoService, actor: Uuid, repo_id: Uuid, user_id: Uuid, ) -> RepoResult { let _ = actor; let db = svc.db.reader(); let collab = collab_entity::Entity::find() .filter(collab_entity::Column::RepoId.eq(repo_id)) .filter(collab_entity::Column::UserId.eq(user_id)) .one(&db) .await?; let permission = match collab { Some(c) => mode_to_str(c.mode), None => "none".to_string(), }; Ok(RepoCollaboratorPermission { permission, role: "collaborator".to_string(), user: UserSummary { id: user_id, name: String::new(), login: String::new(), email: None, }, }) }