Skip to content
Clone

feat(project): add links usecase

zhenyi committed
parent 419ad6f commit cdec836 Browse files

1 file changed

+118 −0

Added lib/project/features/links/usecase.rs View lib/project/features/links/usecase.rs
@@ -0,0 +1,118 @@
1use db::project::project_links as link_entity;
2use db::project::project_links::Column as Lc;
3use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
4use time::OffsetDateTime;
5use uuid::Uuid;
6
7use crate::error::{ProjectError, ProjectResult};
8use crate::features::lifecycle::usecase::log_activity;
9use crate::features::ProjectService;
10use crate::permission;
11
12use super::types::{AddLinkCmd, LinkKind, LinkPage, LinkView};
13
14pub async fn add_link(
15 svc: &ProjectService,
16 actor: Uuid,
17 space_id: Uuid,
18 project_id: Uuid,
19 cmd: AddLinkCmd,
20) -> ProjectResult<LinkView> {
21 crate::features::authz::require(
22 svc,
23 actor,
24 space_id,
25 project_id,
26 permission::MANAGE_LINKS,
27 )
28 .await?;
29
30 if cmd.related_project_id == project_id {
31 return Err(ProjectError::BadRequest(
32 "a project cannot link to itself".into(),
33 ));
34 }
35
36 let kind: db::project::project_links::ProjectLinkKind = cmd.kind.try_into()?;
37 let id = Uuid::new_v4();
38 let now = OffsetDateTime::now_utc();
39 let row = link_entity::ActiveModel {
40 id: Set(id),
41 project_id: Set(project_id),
42 related_project_id: Set(cmd.related_project_id),
43 kind: Set(kind),
44 created_by: Set(actor),
45 created_at: Set(now),
46 }
47 .insert(&svc.db.writer())
48 .await?;
49
50 log_activity(svc, project_id, actor, "link_added", "link", Some(id), None).await;
51 let _ = svc.cache.invalidate_project(project_id).await;
52 Ok(link_view(&row))
53}
54
55pub async fn list_links(
56 svc: &ProjectService,
57 actor: Uuid,
58 space_id: Uuid,
59 project_id: Uuid,
60) -> ProjectResult<LinkPage> {
61 crate::features::authz::require(svc, actor, space_id, project_id, permission::VIEW_PROJECT)
62 .await?;
63 let rows = link_entity::Entity::find()
64 .filter(Lc::ProjectId.eq(project_id))
65 .order_by_desc(Lc::CreatedAt)
66 .all(&svc.db.reader())
67 .await?;
68 let items = rows.iter().map(link_view).collect::<Vec<_>>();
69 let total = items.len() as u64;
70 Ok(LinkPage { total, items })
71}
72
73pub async fn remove_link(
74 svc: &ProjectService,
75 actor: Uuid,
76 space_id: Uuid,
77 project_id: Uuid,
78 link_id: Uuid,
79) -> ProjectResult<()> {
80 crate::features::authz::require(
81 svc,
82 actor,
83 space_id,
84 project_id,
85 permission::MANAGE_LINKS,
86 )
87 .await?;
88
89 let deleted = link_entity::Entity::delete_many()
90 .filter(Lc::Id.eq(link_id))
91 .filter(Lc::ProjectId.eq(project_id))
92 .exec(&svc.db.writer())
93 .await?;
94 if deleted.rows_affected == 0 {
95 return Err(ProjectError::NotFound(format!(
96 "link {link_id} in project {project_id}"
97 )));
98 }
99
100 log_activity(svc, project_id, actor, "link_removed", "link", Some(link_id), None).await;
101 let _ = svc.cache.invalidate_project(project_id).await;
102 Ok(())
103}
104
105fn link_view(m: &link_entity::Model) -> LinkView {
106 LinkView {
107 id: m.id,
108 project_id: m.project_id,
109 related_project_id: m.related_project_id,
110 kind: m.kind.into(),
111 created_by: m.created_by,
112 created_at: m.created_at,
113 }
114}
115
116pub fn links_to_kind(_k: LinkKind) -> LinkKind {
117 _k
118}
No newline at end of file