use db::project::project_links as link_entity; use db::project::project_links::Column as Lc; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set}; use time::OffsetDateTime; use uuid::Uuid; use crate::error::{ProjectError, ProjectResult}; use crate::features::lifecycle::usecase::log_activity; use crate::features::ProjectService; use crate::permission; use super::types::{AddLinkCmd, LinkKind, LinkPage, LinkView}; pub async fn add_link( svc: &ProjectService, actor: Uuid, space_id: Uuid, project_id: Uuid, cmd: AddLinkCmd, ) -> ProjectResult { crate::features::authz::require( svc, actor, space_id, project_id, permission::MANAGE_LINKS, ) .await?; if cmd.related_project_id == project_id { return Err(ProjectError::BadRequest( "a project cannot link to itself".into(), )); } let kind: db::project::project_links::ProjectLinkKind = cmd.kind.try_into()?; let id = Uuid::new_v4(); let now = OffsetDateTime::now_utc(); let row = link_entity::ActiveModel { id: Set(id), project_id: Set(project_id), related_project_id: Set(cmd.related_project_id), kind: Set(kind), created_by: Set(actor), created_at: Set(now), } .insert(&svc.db.writer()) .await?; log_activity(svc, project_id, actor, "link_added", "link", Some(id), None).await; let _ = svc.cache.invalidate_project(project_id).await; Ok(link_view(&row)) } pub async fn list_links( svc: &ProjectService, actor: Uuid, space_id: Uuid, project_id: Uuid, ) -> ProjectResult { crate::features::authz::require(svc, actor, space_id, project_id, permission::VIEW_PROJECT) .await?; let rows = link_entity::Entity::find() .filter(Lc::ProjectId.eq(project_id)) .order_by_desc(Lc::CreatedAt) .all(&svc.db.reader()) .await?; let items = rows.iter().map(link_view).collect::>(); let total = items.len() as u64; Ok(LinkPage { total, items }) } pub async fn remove_link( svc: &ProjectService, actor: Uuid, space_id: Uuid, project_id: Uuid, link_id: Uuid, ) -> ProjectResult<()> { crate::features::authz::require( svc, actor, space_id, project_id, permission::MANAGE_LINKS, ) .await?; let deleted = link_entity::Entity::delete_many() .filter(Lc::Id.eq(link_id)) .filter(Lc::ProjectId.eq(project_id)) .exec(&svc.db.writer()) .await?; if deleted.rows_affected == 0 { return Err(ProjectError::NotFound(format!( "link {link_id} in project {project_id}" ))); } log_activity(svc, project_id, actor, "link_removed", "link", Some(link_id), None).await; let _ = svc.cache.invalidate_project(project_id).await; Ok(()) } fn link_view(m: &link_entity::Model) -> LinkView { LinkView { id: m.id, project_id: m.project_id, related_project_id: m.related_project_id, kind: m.kind.into(), created_by: m.created_by, created_at: m.created_at, } } pub fn links_to_kind(_k: LinkKind) -> LinkKind { _k }