Skip to content
Clone

refactor(member): streamline member summary retrieval and enhance kernel loading

ZhenYi committed
parent 7a80e88 commit 6d6fcf8 Browse files

4 files changed

+196 −88

File tree 4
Modified lib/service/space/src/services/member.rs View lib/service/space/src/services/member.rs
@@ -152,11 +152,7 @@
152152 .offset(offset)
153153 .all(self.access.database.read())
154154 .await?;
155 let mut out = Vec::with_capacity(rows.len());
156 for row in rows {
157 out.push(self.member_summary(&row).await?);
158 }
159 Ok(out)
155 self.member_summaries(&rows).await
160156 }
161157
162158 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %envelope::context::resolve_request_id(request_id)))]
@@ -786,11 +782,7 @@
786782 .offset(offset)
787783 .all(self.access.database.read())
788784 .await?;
789 let mut out = Vec::with_capacity(rows.len());
790 for row in rows {
791 out.push(self.member_summary(&row).await?);
792 }
793 Ok(out)
785 self.member_summaries(&rows).await
794786 }
795787
796788 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, user_id = user_id, request_id = %envelope::context::resolve_request_id(request_id)))]
Modified lib/service/space/src/services/permission.rs View lib/service/space/src/services/permission.rs
@@ -36,19 +36,13 @@
3636 }
3737}
3838
39pub(crate) async fn load_kernel<C: ConnectionTrait>(
40 db: &C,
39pub(crate) fn kernel_from<'a>(
4140 version: i32,
4241 membership: &space_members::Model,
43) -> SpaceResult<Kernel> {
44 let role_ids = space_member_roles::Entity::find()
45 .select_only()
46 .column(space_member_roles::Column::RoleId)
47 .filter(space_member_roles::Column::MemberId.eq(membership.id))
48 .into_tuple::<Uuid>()
49 .all(db)
50 .await?;
51 let role_keys = role::keys_of_ids(db, &role_ids).await?;
42 role_keys: Vec<String>,
43 role_grants: impl IntoIterator<Item = &'a space_role_permissions::Model>,
44 direct_grants: impl IntoIterator<Item = &'a space_member_permissions::Model>,
45) -> Kernel {
5246 let is_owner = membership.role == SpaceMemberRole::Owner
5347 || role_keys.iter().any(|key| key == SYSTEM_ROLE_OWNER);
5448 let is_admin = is_owner
@@ -63,27 +57,17 @@
6357 ..Default::default()
6458 };
6559
66 if !role_ids.is_empty() {
67 let grants = space_role_permissions::Entity::find()
68 .filter(space_role_permissions::Column::RoleId.is_in(role_ids))
69 .all(db)
70 .await?;
71 for grant in grants {
72 if !is_known_permission(&grant.permission) {
73 continue;
74 }
75 match grant.effect {
76 PermissionEffect::Deny => push_unique(&mut kernel.role_deny, &grant.permission),
77 PermissionEffect::Allow => push_unique(&mut kernel.role_allow, &grant.permission),
78 }
60 for grant in role_grants {
61 if !is_known_permission(&grant.permission) {
62 continue;
63 }
64 match grant.effect {
65 PermissionEffect::Deny => push_unique(&mut kernel.role_deny, &grant.permission),
66 PermissionEffect::Allow => push_unique(&mut kernel.role_allow, &grant.permission),
7967 }
8068 }
8169
82 let direct = space_member_permissions::Entity::find()
83 .filter(space_member_permissions::Column::MemberId.eq(membership.id))
84 .all(db)
85 .await?;
86 for grant in direct {
70 for grant in direct_grants {
8771 if !is_known_permission(&grant.permission) {
8872 continue;
8973 }
@@ -93,9 +77,45 @@
9377 }
9478 }
9579
96 Ok(kernel)
80 kernel
9781}
9882
83pub(crate) async fn load_kernel<C: ConnectionTrait>(
84 db: &C,
85 version: i32,
86 membership: &space_members::Model,
87) -> SpaceResult<Kernel> {
88 let role_ids = space_member_roles::Entity::find()
89 .select_only()
90 .column(space_member_roles::Column::RoleId)
91 .filter(space_member_roles::Column::MemberId.eq(membership.id))
92 .into_tuple::<Uuid>()
93 .all(db)
94 .await?;
95 let role_keys = role::keys_of_ids(db, &role_ids).await?;
96
97 let mut role_grants = Vec::new();
98 if !role_ids.is_empty() {
99 role_grants = space_role_permissions::Entity::find()
100 .filter(space_role_permissions::Column::RoleId.is_in(role_ids))
101 .all(db)
102 .await?;
103 }
104
105 let direct = space_member_permissions::Entity::find()
106 .filter(space_member_permissions::Column::MemberId.eq(membership.id))
107 .all(db)
108 .await?;
109
110 Ok(kernel_from(
111 version,
112 membership,
113 role_keys,
114 &role_grants,
115 &direct,
116 ))
117}
118
99119fn decide(kernel: &Kernel, permission: &str) -> (&'static str, &'static str) {
100120 if !is_known_permission(permission) {
101121 return ("unknown_permission", "default_deny");
@@ -123,7 +143,7 @@
123143 ("no_grant", "default_deny")
124144}
125145
126fn allowed_list(kernel: &Kernel) -> Vec<String> {
146pub(crate) fn allowed_list(kernel: &Kernel) -> Vec<String> {
127147 PERMISSIONS
128148 .iter()
129149 .filter(|permission| decide(kernel, permission).0.is_empty())
@@ -350,15 +370,6 @@
350370 })
351371 .collect())
352372 }
353}
354
355/// 列表展示用的有效权限集,复用同一条解析链。
356pub(crate) async fn effective<C: ConnectionTrait>(
357 db: &C,
358 membership: &space_members::Model,
359) -> SpaceResult<Vec<String>> {
360 let kernel = load_kernel(db, 0, membership).await?;
361 Ok(allowed_list(&kernel))
362373}
363374
364375pub(crate) async fn effective_for(
Modified lib/service/space/src/services/service.rs View lib/service/space/src/services/service.rs
@@ -1,3 +1,5 @@
1use std::collections::HashMap;
2
13use crate::errors::{SpaceError, SpaceResult};
24use crate::services::{error, member, permission, role};
35use chrono::Utc;
@@ -882,45 +884,146 @@
882884 .ok_or(SpaceError::SpaceNotFound)
883885 }
884886
885 pub async fn member_summary(&self, row: &space_members::Model) -> SpaceResult<MemberSummary> {
886 let user = users::Entity::find_by_id(row.user_id)
887 .one(self.access.database.read())
887 /// 一次取齐一页成员的用户名、角色与权限,避免逐行回表。
888 pub async fn member_summaries(
889 &self,
890 rows: &[space_members::Model],
891 ) -> SpaceResult<Vec<MemberSummary>> {
892 if rows.is_empty() {
893 return Ok(Vec::new());
894 }
895 let member_ids: Vec<Uuid> = rows.iter().map(|row| row.id).collect();
896 let user_ids: Vec<i64> = rows.iter().map(|row| row.user_id).collect();
897
898 let users: HashMap<i64, (String, Option<String>)> = users::Entity::find()
899 .select_only()
900 .column(users::Column::Id)
901 .column(users::Column::Username)
902 .column(users::Column::Displayname)
903 .filter(users::Column::Id.is_in(user_ids))
904 .into_tuple::<(i64, String, Option<String>)>()
905 .all(self.access.database.read())
906 .await?
907 .into_iter()
908 .map(|(id, username, displayname)| (id, (username, displayname)))
909 .collect();
910
911 let bindings = space_member_roles::Entity::find()
912 .filter(space_member_roles::Column::MemberId.is_in(member_ids.clone()))
913 .all(self.access.database.read())
888914 .await?;
889 Ok(MemberSummary {
890 id: row.id,
891 organization_id: row.organization_id,
892 user_id: row.user_id,
893 username: user
894 .as_ref()
895 .map(|u| u.username.clone())
896 .unwrap_or_default(),
897 displayname: user.and_then(|u| u.displayname),
898 role: row.role,
899 role_keys: role::role_keys_of(self.access.database.read(), row.id).await?,
900 permissions: permission::effective(self.access.database.read(), row).await?,
901 joined_at: row.joined_at,
902 left_at: row.left_at,
903 banned_at: row.banned_at,
904 invited_by: row.invited_by,
905 join_method: row.join_method,
906 alias_name: row.alias_name.clone(),
907 alias_avatar_url: row.alias_avatar_url.clone(),
908 message_notifications: row.message_notifications,
909 muted: row.muted,
910 mute_until: row.mute_until,
911 mobile_push: row.mobile_push,
912 suppress_everyone: row.suppress_everyone,
913 suppress_roles: row.suppress_roles,
914 hide_muted_channels: row.hide_muted_channels,
915 ban_reason: row.ban_reason.clone(),
916 ban_expires_at: row.ban_expires_at,
917 ban_allow_agent_join: row.ban_allow_agent_join,
918 ban_allow_speak: row.ban_allow_speak,
919 ban_allow_push_repo: row.ban_allow_push_repo,
920 ban_allow_comment_any: row.ban_allow_comment_any,
921 version: row.version,
922 last_active_at: row.last_active_at,
923 })
915 let mut role_ids_by_member: HashMap<Uuid, Vec<Uuid>> = HashMap::new();
916 let mut role_ids: Vec<Uuid> = Vec::new();
917 for binding in bindings {
918 role_ids_by_member
919 .entry(binding.member_id)
920 .or_default()
921 .push(binding.role_id);
922 if !role_ids.contains(&binding.role_id) {
923 role_ids.push(binding.role_id);
924 }
925 }
926
927 let roles: HashMap<Uuid, space_roles::Model> = if role_ids.is_empty() {
928 HashMap::new()
929 } else {
930 space_roles::Entity::find()
931 .filter(space_roles::Column::Id.is_in(role_ids.clone()))
932 .all(self.access.database.read())
933 .await?
934 .into_iter()
935 .map(|role| (role.id, role))
936 .collect()
937 };
938
939 let mut role_grants: HashMap<Uuid, Vec<space_role_permissions::Model>> = HashMap::new();
940 if !role_ids.is_empty() {
941 let grants = space_role_permissions::Entity::find()
942 .filter(space_role_permissions::Column::RoleId.is_in(role_ids))
943 .all(self.access.database.read())
944 .await?;
945 for grant in grants {
946 role_grants.entry(grant.role_id).or_default().push(grant);
947 }
948 }
949
950 let mut direct_grants: HashMap<Uuid, Vec<space_member_permissions::Model>> = HashMap::new();
951 let direct = space_member_permissions::Entity::find()
952 .filter(space_member_permissions::Column::MemberId.is_in(member_ids))
953 .all(self.access.database.read())
954 .await?;
955 for grant in direct {
956 direct_grants
957 .entry(grant.member_id)
958 .or_default()
959 .push(grant);
960 }
961
962 let mut out = Vec::with_capacity(rows.len());
963 for row in rows {
964 let (username, displayname) = users.get(&row.user_id).cloned().unwrap_or_default();
965 let member_role_ids = role_ids_by_member
966 .get(&row.id)
967 .map(Vec::as_slice)
968 .unwrap_or_default();
969 let mut role_keys: Vec<(i32, String)> = member_role_ids
970 .iter()
971 .filter_map(|role_id| roles.get(role_id))
972 .map(|role| (role.rank, role.key.clone()))
973 .collect();
974 role_keys.sort_by_key(|(rank, _)| *rank);
975 let kernel = permission::kernel_from(
976 0,
977 row,
978 role_keys.into_iter().map(|(_, key)| key).collect(),
979 member_role_ids
980 .iter()
981 .filter_map(|role_id| role_grants.get(role_id))
982 .flatten(),
983 direct_grants.get(&row.id).into_iter().flatten(),
984 );
985 out.push(MemberSummary {
986 id: row.id,
987 organization_id: row.organization_id,
988 user_id: row.user_id,
989 username,
990 displayname,
991 role: row.role,
992 role_keys: kernel.role_keys.clone(),
993 permissions: permission::allowed_list(&kernel),
994 joined_at: row.joined_at,
995 left_at: row.left_at,
996 banned_at: row.banned_at,
997 invited_by: row.invited_by,
998 join_method: row.join_method,
999 alias_name: row.alias_name.clone(),
1000 alias_avatar_url: row.alias_avatar_url.clone(),
1001 message_notifications: row.message_notifications,
1002 muted: row.muted,
1003 mute_until: row.mute_until,
1004 mobile_push: row.mobile_push,
1005 suppress_everyone: row.suppress_everyone,
1006 suppress_roles: row.suppress_roles,
1007 hide_muted_channels: row.hide_muted_channels,
1008 ban_reason: row.ban_reason.clone(),
1009 ban_expires_at: row.ban_expires_at,
1010 ban_allow_agent_join: row.ban_allow_agent_join,
1011 ban_allow_speak: row.ban_allow_speak,
1012 ban_allow_push_repo: row.ban_allow_push_repo,
1013 ban_allow_comment_any: row.ban_allow_comment_any,
1014 version: row.version,
1015 last_active_at: row.last_active_at,
1016 });
1017 }
1018 Ok(out)
1019 }
1020
1021 pub async fn member_summary(&self, row: &space_members::Model) -> SpaceResult<MemberSummary> {
1022 self.member_summaries(std::slice::from_ref(row))
1023 .await?
1024 .into_iter()
1025 .next()
1026 .ok_or(SpaceError::SpaceMemberNotFound)
9241027 }
9251028
9261029 pub(crate) async fn role_summary(&self, row: &space_roles::Model) -> SpaceResult<RoleSummary> {
Modified lib/share/models/src/space/space_members.rs View lib/share/models/src/space/space_members.rs
@@ -68,6 +68,8 @@
6868
6969 #[sea_orm(belongs_to, from = "organization_id", to = "id", on_delete = "Cascade")]
7070 pub organization: BelongsTo<super::spaces::Entity>,
71 #[sea_orm(belongs_to, from = "user_id", to = "id")]
72 pub user: BelongsTo<super::super::users::users::Entity>,
7173}
7274
7375#[async_trait::async_trait]