Skip to content
Clone

feat(project): add favorites usecase

zhenyi committed
parent ef22532 commit 3871a3a Browse files

1 file changed

+102 −0

Added lib/project/features/favorites/usecase.rs View lib/project/features/favorites/usecase.rs
@@ -0,0 +1,102 @@
1use db::project::project_favorites as fav_entity;
2use db::project::project_favorites::Column as Fc;
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::{AddFavoriteCmd, FavoritePage, FavoriteView};
13
14pub async fn add_favorite(
15 svc: &ProjectService,
16 actor: Uuid,
17 space_id: Uuid,
18 project_id: Uuid,
19 cmd: AddFavoriteCmd,
20) -> ProjectResult<FavoriteView> {
21 crate::features::authz::require(svc, actor, space_id, project_id, permission::STAR).await?;
22
23 let existing = fav_entity::Entity::find()
24 .filter(Fc::ProjectId.eq(project_id))
25 .filter(Fc::UserId.eq(actor))
26 .one(&svc.db.reader())
27 .await?;
28
29 let row = if let Some(m) = existing {
30 let mut active: fav_entity::ActiveModel = m.into();
31 active.folder = Set(cmd.folder);
32 active.update(&svc.db.writer()).await?
33 } else {
34 fav_entity::ActiveModel {
35 id: Set(Uuid::new_v4()),
36 project_id: Set(project_id),
37 user_id: Set(actor),
38 folder: Set(cmd.folder),
39 created_at: Set(OffsetDateTime::now_utc()),
40 }
41 .insert(&svc.db.writer())
42 .await?
43 };
44
45 let _ = svc.cache.invalidate_favorites(actor).await;
46 log_activity(svc, project_id, actor, "favorited", "favorite", None, None).await;
47 Ok(favorite_view(&row))
48}
49
50pub async fn list_favorites(
51 svc: &ProjectService,
52 actor: Uuid,
53 space_id: Uuid,
54 project_id: Uuid,
55) -> ProjectResult<FavoritePage> {
56 crate::features::authz::require(svc, actor, space_id, project_id, permission::VIEW_PROJECT)
57 .await?;
58 let rows = fav_entity::Entity::find()
59 .filter(Fc::ProjectId.eq(project_id))
60 .order_by_desc(Fc::CreatedAt)
61 .all(&svc.db.reader())
62 .await?;
63 let items = rows.iter().map(favorite_view).collect::<Vec<_>>();
64 let total = items.len() as u64;
65 Ok(FavoritePage { total, items })
66}
67
68pub async fn remove_favorite(
69 svc: &ProjectService,
70 actor: Uuid,
71 space_id: Uuid,
72 project_id: Uuid,
73 favorite_id: Uuid,
74) -> ProjectResult<()> {
75 crate::features::authz::require(svc, actor, space_id, project_id, permission::STAR).await?;
76
77 let deleted = fav_entity::Entity::delete_many()
78 .filter(Fc::Id.eq(favorite_id))
79 .filter(Fc::ProjectId.eq(project_id))
80 .filter(Fc::UserId.eq(actor))
81 .exec(&svc.db.writer())
82 .await?;
83 if deleted.rows_affected == 0 {
84 return Err(ProjectError::NotFound(format!(
85 "favorite {favorite_id} in project {project_id}"
86 )));
87 }
88
89 let _ = svc.cache.invalidate_favorites(actor).await;
90 log_activity(svc, project_id, actor, "unfavorited", "favorite", None, None).await;
91 Ok(())
92}
93
94fn favorite_view(m: &fav_entity::Model) -> FavoriteView {
95 FavoriteView {
96 id: m.id,
97 project_id: m.project_id,
98 user_id: m.user_id,
99 folder: m.folder.clone(),
100 created_at: m.created_at,
101 }
102}
No newline at end of file