refactor(space): refresh space crate (features, handlers, lookup) — excluding sidebar modules
zhenyi committed
Modified lib/space/features/bans/usecase.rs lib/space/features/bans/usecase.rs +40 −4 View lib/space/features/bans/usecase.rs
@@ -1,6 +1,8 @@
11use db::conn::sea_orm::*;
22use db::space::space_bans as ban_entity;
33use db::space::spaces as space_entity;
4use db::user::users_loader::{load_user, load_users};
5use export::user::basic::UserBasicInfo;
46use time::OffsetDateTime;
57use uuid::Uuid;
68
@@ -72,7 +74,11 @@
7274 .set_allow_comment_any(cmd.allow_comment_any.unwrap_or(false))
7375 .into();
7476 let saved = active.insert(&db).await?;
75 Ok(saved.into())
77 let user = load_user(&svc.db, cmd.user_id)
78 .await?
79 .map(UserBasicInfo::from);
80 let banned_by = load_user(&svc.db, caller).await?.map(UserBasicInfo::from);
81 Ok(SpaceBanView::from_model(saved, user, banned_by))
7682 }
7783 .await;
7884 metrics::record_outcome(metrics::FEATURE_BANS_CREATE, &result);
@@ -93,7 +99,15 @@
9399 .one(&db)
94100 .await?
95101 .ok_or_else(|| SpaceError::NotFound(format!("ban {user_id} in space {space_id}")))?;
96 Ok(ban.into())
102 let target_user_id = ban.user_id;
103 let banned_by_id = ban.banned_by;
104 let user = load_user(&svc.db, target_user_id)
105 .await?
106 .map(UserBasicInfo::from);
107 let banned_by = load_user(&svc.db, banned_by_id)
108 .await?
109 .map(UserBasicInfo::from);
110 Ok(SpaceBanView::from_model(ban, user, banned_by))
97111 }
98112 .await;
99113 metrics::record_outcome(metrics::FEATURE_BANS_GET, &result);
@@ -116,6 +130,8 @@
116130 .one(&txn)
117131 .await?
118132 .ok_or_else(|| SpaceError::NotFound(format!("ban {user_id} in space {space_id}")))?;
133 let target_user_id = ban.user_id;
134 let banned_by_id = ban.banned_by;
119135 let mut active: ban_entity::ActiveModel = ban.into();
120136 if let Some(reason) = cmd.reason {
121137 active.reason = Set(reason);
@@ -138,7 +154,13 @@
138154 active.updated_at = Set(OffsetDateTime::now_utc());
139155 let saved = active.update(&txn).await?;
140156 txn.commit().await?;
141 Ok(saved.into())
157 let user = load_user(&svc.db, target_user_id)
158 .await?
159 .map(UserBasicInfo::from);
160 let banned_by = load_user(&svc.db, banned_by_id)
161 .await?
162 .map(UserBasicInfo::from);
163 Ok(SpaceBanView::from_model(saved, user, banned_by))
142164 }
143165 .await;
144166 metrics::record_outcome(metrics::FEATURE_BANS_UPDATE, &result);
@@ -195,8 +217,22 @@
195217 .filter(ban_entity::Column::SpaceId.eq(space_id))
196218 .count(&db)
197219 .await?;
220 let mut ids: Vec<Uuid> = items.iter().map(|b| b.user_id).collect();
221 ids.extend(items.iter().map(|b| b.banned_by));
222 let users = load_users(&svc.db, &ids).await?;
198223 Ok(SpaceBanPage {
199 items: items.into_iter().map(SpaceBanView::from).collect(),
224 items: items
225 .into_iter()
226 .map(|b| {
227 let user_id = b.user_id;
228 let banned_by_id = b.banned_by;
229 SpaceBanView::from_model(
230 b,
231 users.get(&user_id).map(UserBasicInfo::from),
232 users.get(&banned_by_id).map(UserBasicInfo::from),
233 )
234 })
235 .collect(),
200236 total: total as i64,
201237 })
202238 }
Modified lib/space/features/channels/usecase.rs lib/space/features/channels/usecase.rs +40 −6 View lib/space/features/channels/usecase.rs
@@ -2,6 +2,9 @@
22use db::conn::sea_orm::*;
33use db::space::space_members as member_entity;
44use db::space::spaces as space_entity;
5use db::user::users as user_entity;
6use db::user::users_loader::{load_user, load_users};
7use export::user::basic::UserBasicInfo;
58use time::OffsetDateTime;
69use uuid::Uuid;
710
@@ -53,7 +56,21 @@
5356 if channel.space_id != Some(space_id) {
5457 return Err(SpaceError::NotFound(format!("channel {channel_id}")));
5558 }
56 Ok(ChannelView::from(channel))
59 let created_by = channel.created_by;
60 let archived_by = channel.archived_by;
61 let created_by_user = load_users(&svc.db, &[created_by])
62 .await?
63 .get(&created_by)
64 .map(UserBasicInfo::from);
65 let archived_by_user = match archived_by {
66 Some(id) => load_user(&svc.db, id).await?.map(UserBasicInfo::from),
67 None => None,
68 };
69 Ok(ChannelView::from_model(
70 channel,
71 archived_by_user,
72 created_by_user,
73 ))
5774 }
5875 .await;
5976 metrics::record_outcome(metrics::FEATURE_CHANNELS_GET, &result);
@@ -72,15 +89,27 @@
7289 member_space(svc, caller, space_id).await?;
7390 let db = svc.db.reader();
7491 let limit = std::cmp::max(std::cmp::min(limit, svc.config.max_pagination), 1);
75 let items = channel_entity::Entity::find()
92 let rows = channel_entity::Entity::find()
7693 .filter(channel_entity::Column::SpaceId.eq(space_id))
7794 .order_by_asc(channel_entity::Column::Position)
7895 .limit(Some(limit))
7996 .offset(Some(offset))
8097 .all(&db)
81 .await?
98 .await?;
99 let mut user_ids: Vec<Uuid> = rows.iter().map(|c| c.created_by).collect();
100 user_ids.extend(rows.iter().filter_map(|c| c.archived_by));
101 let users = load_users(&svc.db, &user_ids).await?;
102 let items = rows
82103 .into_iter()
83 .map(ChannelView::from)
104 .map(|c| {
105 let created_by = c.created_by;
106 let archived_by = c.archived_by;
107 ChannelView::from_model(
108 c,
109 archived_by.and_then(|id| users.get(&id).map(UserBasicInfo::from)),
110 users.get(&created_by).map(UserBasicInfo::from),
111 )
112 })
84113 .collect();
85114 let total = channel_entity::Entity::find()
86115 .filter(channel_entity::Column::SpaceId.eq(space_id))
@@ -153,7 +182,8 @@
153182 .set_created_by(caller)
154183 .into();
155184 let saved = active.insert(&db).await?;
156 Ok(ChannelView::from(saved))
185 let created_by_user = load_user(&svc.db, caller).await?.map(UserBasicInfo::from);
186 Ok(ChannelView::from_model(saved, None, created_by_user))
157187 }
158188 .await;
159189 metrics::record_outcome(metrics::FEATURE_CHANNELS_ENSURE, &result);
@@ -192,7 +222,11 @@
192222 let saved = channel_entity::Entity::insert(active)
193223 .exec_with_returning(db)
194224 .await?;
195 Ok(ChannelView::from(saved))
225 let created_by_user = user_entity::Entity::find_by_id(created_by)
226 .one(db)
227 .await?
228 .map(UserBasicInfo::from);
229 Ok(ChannelView::from_model(saved, None, created_by_user))
196230 }
197231 .await;
198232 metrics::record_outcome(metrics::FEATURE_CHANNELS_ENSURE, &result);
Modified lib/space/features/invites/usecase.rs lib/space/features/invites/usecase.rs +192 −36 View lib/space/features/invites/usecase.rs
@@ -1,7 +1,13 @@
11use db::conn::sea_orm::*;
22use db::space::space_invite as invite_entity;
33use db::space::space_member_roles as member_role_entity;
4use db::space::space_members as member_entity;
5use db::space::space_roles as role_entity;
46use db::space::spaces as space_entity;
7use db::user::user_emails as email_entity;
8use db::user::users as user_entity;
9use db::user::users_loader::{load_user, load_users};
10use export::user::basic::UserBasicInfo;
511use time::OffsetDateTime;
612use uuid::Uuid;
713
@@ -12,22 +18,12 @@
1218
1319use super::{AcceptInviteCmd, CreateInviteCmd, SpaceInvitePage, SpaceInviteView};
1420
15async fn owner_space(
21async fn manage_invites_space(
1622 svc: &SpaceService,
1723 caller: Uuid,
1824 space_id: Uuid,
1925) -> SpaceResult<space_entity::Model> {
20 let db = svc.db.reader();
21 let space = space_entity::Entity::find_by_id(space_id)
22 .one(&db)
23 .await?
24 .ok_or_else(|| SpaceError::NotFound(format!("space {space_id}")))?;
25 if space.owner_id != caller {
26 return Err(SpaceError::Forbidden(
27 "only the owner can manage invites".to_string(),
28 ));
29 }
30 Ok(space)
26 authz::require_perm(svc, caller, space_id, authz::MANAGE_INVITES).await
3127}
3228
3329fn parse_ts(s: Option<&str>) -> SpaceResult<Option<OffsetDateTime>> {
@@ -39,6 +35,25 @@
3935 }
4036}
4137
38fn normalize_email(email: Option<String>, max_len: usize) -> SpaceResult<Option<String>> {
39 let Some(email) = email else {
40 return Ok(None);
41 };
42 let email = email.trim().to_lowercase();
43 if email.is_empty() {
44 return Ok(None);
45 }
46 if email.chars().count() > max_len
47 || email.chars().any(char::is_whitespace)
48 || !email.contains('@')
49 || email.starts_with('@')
50 || email.ends_with('@')
51 {
52 return Err(SpaceError::BadRequest("invalid invite email".to_string()));
53 }
54 Ok(Some(email))
55}
56
4257pub async fn create_invite(
4358 svc: &SpaceService,
4459 caller: Uuid,
@@ -47,17 +62,74 @@
4762) -> SpaceResult<SpaceInviteView> {
4863 let _timer = metrics::Timer::start(metrics::FEATURE_INVITES_CREATE);
4964 let result: SpaceResult<SpaceInviteView> = async {
50 owner_space(svc, caller, space_id).await?;
51 if cmd.user_id.is_none() && cmd.email.is_none() {
65 manage_invites_space(svc, caller, space_id).await?;
66 let CreateInviteCmd {
67 user_id: requested_user_id,
68 email: requested_email,
69 mut role_ids,
70 expires_at,
71 } = cmd;
72 let email = normalize_email(requested_email, svc.config.max_invite_email_len)?;
73 if requested_user_id.is_none() && email.is_none() {
5274 return Err(SpaceError::BadRequest(
5375 "either user_id or email must be provided".to_string(),
5476 ));
5577 }
56 if let Some(user_id) = cmd.user_id {
57 let db = svc.db.reader();
78 if role_ids.is_empty() {
79 return Err(SpaceError::BadRequest(
80 "at least one role is required".to_string(),
81 ));
82 }
83 role_ids.sort_unstable();
84 role_ids.dedup();
85 let db = svc.db.reader();
86 let mut user_id = requested_user_id;
87 if let Some(target_user_id) = user_id {
88 let user_exists = user_entity::Entity::find_by_id(target_user_id)
89 .one(&db)
90 .await?
91 .is_some();
92 if !user_exists {
93 return Err(SpaceError::NotFound(format!("user {target_user_id}")));
94 }
95 if let Some(email) = email.as_deref() {
96 let matches_user = email_entity::Entity::find()
97 .filter(email_entity::Column::UserId.eq(target_user_id))
98 .filter(email_entity::Column::LowerEmail.eq(email))
99 .filter(email_entity::Column::IsActivated.eq(true))
100 .filter(email_entity::Column::DeletedAt.is_null())
101 .one(&db)
102 .await?
103 .is_some();
104 if !matches_user {
105 return Err(SpaceError::BadRequest(
106 "email does not belong to the invited user".to_string(),
107 ));
108 }
109 }
110 } else if let Some(email) = email.as_deref() {
111 user_id = email_entity::Entity::find()
112 .filter(email_entity::Column::LowerEmail.eq(email))
113 .filter(email_entity::Column::IsActivated.eq(true))
114 .filter(email_entity::Column::DeletedAt.is_null())
115 .one(&db)
116 .await?
117 .map(|entry| entry.user_id);
118 }
119 let role_count = role_entity::Entity::find()
120 .filter(role_entity::Column::SpaceId.eq(space_id))
121 .filter(role_entity::Column::Id.is_in(role_ids.clone()))
122 .count(&db)
123 .await?;
124 if role_count != role_ids.len() as u64 {
125 return Err(SpaceError::BadRequest(
126 "one or more roles do not belong to this space".to_string(),
127 ));
128 }
129 if let Some(user_id) = user_id {
58130 let existing = invite_entity::Entity::find()
59131 .filter(invite_entity::Column::SpaceId.eq(space_id))
60 .filter(invite_entity::Column::UserId.eq(user_id))
132 .filter(invite_entity::Column::UserId.eq(Some(user_id)))
61133 .filter(invite_entity::Column::AcceptedAt.is_null())
62134 .filter(invite_entity::Column::RevokedAt.is_null())
63135 .one(&db)
@@ -68,20 +140,26 @@
68140 )));
69141 }
70142 }
71 let email = cmd.email.unwrap_or_default();
72 if email.chars().count() > svc.config.max_invite_email_len {
73 return Err(SpaceError::BadRequest(format!(
74 "email exceeds {} characters",
75 svc.config.max_invite_email_len
76 )));
143 if let Some(email) = email.as_deref() {
144 let existing = invite_entity::Entity::find()
145 .filter(invite_entity::Column::SpaceId.eq(space_id))
146 .filter(invite_entity::Column::Email.eq(email))
147 .filter(invite_entity::Column::AcceptedAt.is_null())
148 .filter(invite_entity::Column::RevokedAt.is_null())
149 .one(&db)
150 .await?;
151 if existing.is_some() {
152 return Err(SpaceError::Conflict(format!(
153 "an active invite already exists for email {email} in space {space_id}"
154 )));
155 }
77156 }
78 let expires_at = parse_ts(cmd.expires_at.as_deref())?;
157 let expires_at = parse_ts(expires_at.as_deref())?;
79158 let db = svc.db.writer();
80159 let now = OffsetDateTime::now_utc();
81160 let id = Uuid::now_v7();
82161 let accept_code = format!("{}-{}", Uuid::now_v7().simple(), Uuid::now_v7().simple());
83 let role_ids = cmd
84 .role_ids
162 let role_ids = role_ids
85163 .iter()
86164 .map(|r| r.to_string())
87165 .collect::<Vec<_>>()
@@ -89,8 +167,8 @@
89167 let invite: invite_entity::ActiveModel = invite_entity::ActiveModel::builder()
90168 .set_id(id)
91169 .set_space_id(space_id)
92 .set_user_id(cmd.user_id.unwrap_or_default())
93 .set_email(email)
170 .set_user_id(user_id)
171 .set_email(email.unwrap_or_default())
94172 .set_role_ids(role_ids)
95173 .set_accept_code(accept_code)
96174 .set_created_at(now)
@@ -99,7 +177,11 @@
99177 .set_revoked_at(None)
100178 .into();
101179 let saved = invite.insert(&db).await?;
102 Ok(SpaceInviteView::from(saved))
180 let user = match user_id {
181 Some(id) => load_user(&svc.db, id).await?.map(UserBasicInfo::from),
182 None => None,
183 };
184 Ok(SpaceInviteView::from_model(saved, user))
103185 }
104186 .await;
105187 metrics::record_outcome(metrics::FEATURE_INVITES_CREATE, &result);
@@ -114,7 +196,7 @@
114196) -> SpaceResult<SpaceInviteView> {
115197 let _timer = metrics::Timer::start(metrics::FEATURE_INVITES_REVOKE);
116198 let result: SpaceResult<SpaceInviteView> = async {
117 owner_space(svc, caller, space_id).await?;
199 manage_invites_space(svc, caller, space_id).await?;
118200 let db = svc.db.writer();
119201 let invite = invite_entity::Entity::find_by_id(invite_id)
120202 .one(&db)
@@ -126,7 +208,11 @@
126208 let mut active: invite_entity::ActiveModel = invite.into();
127209 active.revoked_at = Set(Some(OffsetDateTime::now_utc()));
128210 let saved = active.update(&db).await?;
129 Ok(SpaceInviteView::from(saved))
211 let user = match saved.user_id {
212 Some(id) => load_user(&svc.db, id).await?.map(UserBasicInfo::from),
213 None => None,
214 };
215 Ok(SpaceInviteView::from_model(saved, user))
130216 }
131217 .await;
132218 metrics::record_outcome(metrics::FEATURE_INVITES_REVOKE, &result);
@@ -141,12 +227,26 @@
141227 let _timer = metrics::Timer::start(metrics::FEATURE_INVITES_ACCEPT);
142228 let result: SpaceResult<()> = async {
143229 let db = svc.db.writer();
230 let txn = db.begin().await?;
144231 let invite = invite_entity::Entity::find()
145232 .filter(invite_entity::Column::AcceptCode.eq(&cmd.accept_code))
146 .one(&db)
233 .one(&txn)
147234 .await?
148235 .ok_or_else(|| SpaceError::NotFound("invite".to_string()))?;
149 if invite.user_id != caller {
236 let authorized_by_user_id = invite.user_id == Some(caller);
237 let authorized_by_email = if invite.user_id.is_none() && !invite.email.is_empty() {
238 email_entity::Entity::find()
239 .filter(email_entity::Column::UserId.eq(caller))
240 .filter(email_entity::Column::LowerEmail.eq(&invite.email))
241 .filter(email_entity::Column::IsActivated.eq(true))
242 .filter(email_entity::Column::DeletedAt.is_null())
243 .one(&txn)
244 .await?
245 .is_some()
246 } else {
247 false
248 };
249 if !authorized_by_user_id && !authorized_by_email {
150250 return Err(SpaceError::Forbidden(
151251 "this invite does not belong to you".to_string(),
152252 ));
@@ -167,7 +267,51 @@
167267 .split(',')
168268 .filter_map(|s| Uuid::parse_str(s).ok())
169269 .collect::<Vec<_>>();
270 if role_ids.is_empty() {
271 return Err(SpaceError::Conflict(
272 "invite has no valid roles".to_string(),
273 ));
274 }
275 let role_count = role_entity::Entity::find()
276 .filter(role_entity::Column::SpaceId.eq(invite.space_id))
277 .filter(role_entity::Column::Id.is_in(role_ids.clone()))
278 .count(&txn)
279 .await?;
280 if role_count != role_ids.len() as u64 {
281 return Err(SpaceError::Conflict(
282 "invite contains invalid roles".to_string(),
283 ));
284 }
285 if member_entity::Entity::find_by_id((invite.space_id, caller))
286 .one(&txn)
287 .await?
288 .is_some()
289 {
290 return Err(SpaceError::Conflict(
291 "user is already a member of this space".to_string(),
292 ));
293 }
170294 let now = OffsetDateTime::now_utc();
295 let member: member_entity::ActiveModel = member_entity::ActiveModel::builder()
296 .set_space_id(invite.space_id)
297 .set_user_id(caller)
298 .set_alias_name(None)
299 .set_alias_avatar_url(None)
300 .set_status(member_entity::MemberStatus::Active)
301 .set_invited_at(Some(invite.created_at))
302 .set_invited_by(None)
303 .set_joined_at(now)
304 .set_created_at(now)
305 .set_updated_at(Some(now))
306 .set_message_notifications(member_entity::MessageNotifications::All)
307 .set_muted(false)
308 .set_mute_until(None)
309 .set_mobile_push(false)
310 .set_suppress_everyone(false)
311 .set_suppress_roles(false)
312 .set_hide_muted_channels(false)
313 .into();
314 member.insert(&txn).await?;
171315 for role_id in &role_ids {
172316 let mr: member_role_entity::ActiveModel = member_role_entity::ActiveModel::builder()
173317 .set_space_id(invite.space_id)
@@ -175,11 +319,12 @@
175319 .set_role_id(*role_id)
176320 .set_created_at(now)
177321 .into();
178 mr.insert(&db).await?;
322 mr.insert(&txn).await?;
179323 }
180324 let mut active: invite_entity::ActiveModel = invite.into();
181325 active.accepted_at = Set(Some(now));
182 active.update(&db).await?;
326 active.update(&txn).await?;
327 txn.commit().await?;
183328 Ok(())
184329 }
185330 .await;
@@ -230,8 +375,19 @@
230375 .filter(invite_entity::Column::SpaceId.eq(space_id))
231376 .count(&db)
232377 .await?;
378 let user_ids: Vec<Uuid> = items.iter().filter_map(|i| i.user_id).collect();
379 let users = load_users(&svc.db, &user_ids).await?;
233380 Ok(SpaceInvitePage {
234 items: items.into_iter().map(SpaceInviteView::from).collect(),
381 items: items
382 .into_iter()
383 .map(|i| {
384 let user_id = i.user_id;
385 SpaceInviteView::from_model(
386 i,
387 user_id.and_then(|id| users.get(&id).map(UserBasicInfo::from)),
388 )
389 })
390 .collect(),
235391 total: total as i64,
236392 })
237393 }
Modified lib/space/features/members/usecase.rs lib/space/features/members/usecase.rs +96 −5 View lib/space/features/members/usecase.rs
@@ -1,11 +1,15 @@
1use db::conn::sea_orm::sea_query::OnConflict;
12use db::conn::sea_orm::*;
23use db::space::space_members as member_entity;
34use db::space::spaces as space_entity;
5use db::user::users_loader::{load_user, load_users};
6use export::user::basic::UserBasicInfo;
47use time::OffsetDateTime;
58use uuid::Uuid;
69
710use crate::error::{SpaceError, SpaceResult};
811use crate::features::SpaceService;
12use crate::features::authz;
913use crate::metrics;
1014
1115use super::{AddMemberCmd, SpaceMemberPage, SpaceMemberView, UpdateMemberCmd};
@@ -29,6 +33,57 @@
2933 }
3034}
3135
36async fn ensure_owner_member(svc: &SpaceService, space_id: Uuid) -> SpaceResult<()> {
37 let db = svc.db.writer();
38 let txn = db.begin().await?;
39 let space = space_entity::Entity::find_by_id(space_id)
40 .one(&txn)
41 .await?
42 .ok_or_else(|| SpaceError::NotFound(format!("space {space_id}")))?;
43 let owner_id = space.owner_id;
44
45 if let Some(existing) = member_entity::Entity::find_by_id((space_id, owner_id))
46 .one(&txn)
47 .await?
48 {
49 if existing.status != member_entity::MemberStatus::Active {
50 let mut active: member_entity::ActiveModel = existing.into();
51 let now = OffsetDateTime::now_utc();
52 active.status = Set(member_entity::MemberStatus::Active);
53 active.left_at = Set(None);
54 active.updated_at = Set(Some(now));
55 active.update(&txn).await?;
56 }
57 } else {
58 let owner_member: member_entity::ActiveModel = member_entity::ActiveModel::builder()
59 .set_space_id(space_id)
60 .set_user_id(owner_id)
61 .set_alias_name(Option::<String>::None)
62 .set_alias_avatar_url(Option::<String>::None)
63 .set_status(member_entity::MemberStatus::Active)
64 .set_invited_at(Option::<OffsetDateTime>::None)
65 .set_invited_by(Option::<Uuid>::None)
66 .set_joined_at(space.created_at)
67 .set_created_at(space.created_at)
68 .set_updated_at(Option::<OffsetDateTime>::Some(space.updated_at))
69 .into();
70 member_entity::Entity::insert(owner_member)
71 .on_conflict(
72 OnConflict::columns([
73 member_entity::Column::SpaceId,
74 member_entity::Column::UserId,
75 ])
76 .do_nothing()
77 .to_owned(),
78 )
79 .exec(&txn)
80 .await?;
81 }
82
83 txn.commit().await?;
84 Ok(())
85}
86
3287pub async fn add_member(
3388 svc: &SpaceService,
3489 caller: Uuid,
@@ -72,7 +127,10 @@
72127 .set_hide_muted_channels(cmd.hide_muted_channels.unwrap_or(false))
73128 .into();
74129 let saved: member_entity::Model = active.insert(&db).await?;
75 Ok(saved.into())
130 let user = load_user(&svc.db, cmd.user_id)
131 .await?
132 .map(UserBasicInfo::from);
133 Ok(SpaceMemberView::from_model(saved, user, None))
76134 }
77135 .await;
78136 metrics::record_outcome(metrics::FEATURE_MEMBERS_ADD, &result);
@@ -93,7 +151,16 @@
93151 .one(&db)
94152 .await?
95153 .ok_or_else(|| SpaceError::NotFound(format!("member {user_id} in space {space_id}")))?;
96 Ok(m.into())
154 let target_user_id = m.user_id;
155 let invite_by = m.invited_by;
156 let user = load_user(&svc.db, target_user_id)
157 .await?
158 .map(UserBasicInfo::from);
159 let invited_by = match invite_by {
160 Some(id) => load_user(&svc.db, id).await?.map(UserBasicInfo::from),
161 None => None,
162 };
163 Ok(SpaceMemberView::from_model(m, user, invited_by))
97164 }
98165 .await;
99166 metrics::record_outcome(metrics::FEATURE_MEMBERS_GET, &result);
@@ -126,6 +193,8 @@
126193 .one(&txn)
127194 .await?
128195 .ok_or_else(|| SpaceError::NotFound(format!("member {user_id} in space {space_id}")))?;
196 let target_user_id = m.user_id;
197 let invite_by = m.invited_by;
129198 let mut active: member_entity::ActiveModel = m.into();
130199 if let Some(v) = cmd.alias_name {
131200 match v {
@@ -168,7 +237,14 @@
168237 active.updated_at = Set(Some(OffsetDateTime::now_utc()));
169238 let saved = active.update(&txn).await?;
170239 txn.commit().await?;
171 Ok(saved.into())
240 let user = load_user(&svc.db, target_user_id)
241 .await?
242 .map(UserBasicInfo::from);
243 let invited_by = match invite_by {
244 Some(id) => load_user(&svc.db, id).await?.map(UserBasicInfo::from),
245 None => None,
246 };
247 Ok(SpaceMemberView::from_model(saved, user, invited_by))
172248 }
173249 .await;
174250 metrics::record_outcome(metrics::FEATURE_MEMBERS_UPDATE, &result);
@@ -217,7 +293,8 @@
217293) -> SpaceResult<SpaceMemberPage> {
218294 let _timer = metrics::Timer::start(metrics::FEATURE_MEMBERS_LIST);
219295 let result: SpaceResult<SpaceMemberPage> = async {
220 space(caller, space_id, svc).await?;
296 authz::require_perm(svc, caller, space_id, authz::MANAGE_MEMBERS).await?;
297 ensure_owner_member(svc, space_id).await?;
221298 let db = svc.db.reader();
222299 let limit = std::cmp::max(std::cmp::min(limit, svc.config.max_pagination), 1) as u64;
223300 let items = member_entity::Entity::find()
@@ -231,8 +308,22 @@
231308 .filter(member_entity::Column::SpaceId.eq(space_id))
232309 .count(&db)
233310 .await?;
311 let mut ids: Vec<Uuid> = items.iter().map(|m| m.user_id).collect();
312 ids.extend(items.iter().filter_map(|m| m.invited_by));
313 let users = load_users(&svc.db, &ids).await?;
234314 Ok(SpaceMemberPage {
235 items: items.into_iter().map(SpaceMemberView::from).collect(),
315 items: items
316 .into_iter()
317 .map(|m| {
318 let user_id = m.user_id;
319 let invited_by = m.invited_by;
320 SpaceMemberView::from_model(
321 m,
322 users.get(&user_id).map(UserBasicInfo::from),
323 invited_by.and_then(|id| users.get(&id).map(UserBasicInfo::from)),
324 )
325 })
326 .collect(),
236327 total: total as i64,
237328 })
238329 }
Modified lib/space/features/overwrites/usecase.rs lib/space/features/overwrites/usecase.rs +15 −2 View lib/space/features/overwrites/usecase.rs
@@ -5,6 +5,8 @@
55use db::space::space_members as space_member_entity;
66use db::space::space_roles as space_role_entity;
77use db::space::spaces as space_entity;
8use db::user::users_loader::{load_user, load_users};
9use export::user::basic::UserBasicInfo;
810use uuid::Uuid;
911
1012use crate::error::{SpaceError, SpaceResult};
@@ -198,7 +200,10 @@
198200 .into();
199201 active.insert(&db).await?
200202 };
201 Ok(saved.into())
203 let user = load_user(&svc.db, cmd.user_id)
204 .await?
205 .map(UserBasicInfo::from);
206 Ok(UserOverwriteView::from_model(saved, user))
202207 }
203208 .await;
204209 metrics::record_outcome(metrics::FEATURE_OVERWRITES_SET_USER, &result);
@@ -249,7 +254,15 @@
249254 .filter(user_ow_entity::Column::ChannelId.eq(channel_id))
250255 .all(&db)
251256 .await?;
252 Ok(items.into_iter().map(UserOverwriteView::from).collect())
257 let user_ids: Vec<Uuid> = items.iter().map(|i| i.user_id).collect();
258 let users = load_users(&svc.db, &user_ids).await?;
259 Ok(items
260 .into_iter()
261 .map(|i| {
262 let user_id = i.user_id;
263 UserOverwriteView::from_model(i, users.get(&user_id).map(UserBasicInfo::from))
264 })
265 .collect())
253266 }
254267 .await;
255268 metrics::record_outcome(metrics::FEATURE_OVERWRITES_LIST_USER, &result);
Modified lib/space/features/roles/usecase.rs lib/space/features/roles/usecase.rs +33 −16 View lib/space/features/roles/usecase.rs
@@ -3,6 +3,8 @@
33use db::space::space_role_permissions as role_perm_entity;
44use db::space::space_roles as role_entity;
55use db::space::spaces as space_entity;
6use db::user::users_loader::{load_user, load_users};
7use export::user::basic::UserBasicInfo;
68use time::OffsetDateTime;
79use uuid::Uuid;
810
@@ -133,19 +135,23 @@
133135 replace_permissions(&txn, role_id, &cmd.permissions).await?;
134136 txn.commit().await?;
135137
136 let mut view = RoleView::from(role_entity::Model {
137 id: role_id,
138 space_id,
139 name,
140 position,
141 color,
142 unicode_emoji,
143 icon_hash,
144 version: 1,
145 created_at: now,
146 updated_at: Some(now),
147 created_by: caller,
148 });
138 let created_by_user = load_user(&svc.db, caller).await?.map(UserBasicInfo::from);
139 let mut view = RoleView::from_model(
140 role_entity::Model {
141 id: role_id,
142 space_id,
143 name,
144 position,
145 color,
146 unicode_emoji,
147 icon_hash,
148 version: 1,
149 created_at: now,
150 updated_at: Some(now),
151 created_by: caller,
152 },
153 created_by_user,
154 );
149155 view.permissions = cmd.permissions;
150156 Ok(view)
151157 }
@@ -188,7 +194,10 @@
188194 }
189195 }
190196 let perms = load_permissions(&db, role_id).await?;
191 let mut view = RoleView::from(role);
197 let created_by_user = load_user(&svc.db, role.created_by)
198 .await?
199 .map(UserBasicInfo::from);
200 let mut view = RoleView::from_model(role, created_by_user);
192201 view.permissions = perms;
193202 Ok(view)
194203 }
@@ -269,7 +278,10 @@
269278 txn.commit().await?;
270279
271280 let perms = load_permissions(&svc.db.reader(), role_id).await?;
272 let mut view = RoleView::from(saved);
281 let created_by_user = load_user(&svc.db, saved.created_by)
282 .await?
283 .map(UserBasicInfo::from);
284 let mut view = RoleView::from_model(saved, created_by_user);
273285 view.permissions = perms;
274286 Ok(view)
275287 }
@@ -333,10 +345,15 @@
333345 .count(&db)
334346 .await?;
335347
348 let created_by_ids: Vec<Uuid> = roles.iter().map(|r| r.created_by).collect();
349 let users = load_users(&svc.db, &created_by_ids).await?;
350
336351 let mut views = Vec::with_capacity(roles.len());
337352 for role in roles {
338353 let perms = load_permissions(&db, role.id).await?;
339 let mut view = RoleView::from(role);
354 let created_by = role.created_by;
355 let mut view =
356 RoleView::from_model(role, users.get(&created_by).map(UserBasicInfo::from));
340357 view.permissions = perms;
341358 views.push(view);
342359 }
Modified lib/space/features/spaces/usecase.rs lib/space/features/spaces/usecase.rs +123 −18 View lib/space/features/spaces/usecase.rs
@@ -1,11 +1,14 @@
1use crate::error::{SpaceError, SpaceResult};
2use crate::features::SpaceService;
3use crate::features::authz;
4use crate::metrics;
15use db::conn::sea_orm::*;
6use db::space::space_members as member_entity;
27use db::space::spaces as space_entity;
8use db::user::users_loader::{load_user, load_users};
9use export::user::basic::UserBasicInfo;
310use time::OffsetDateTime;
411use uuid::Uuid;
5use crate::error::{SpaceError, SpaceResult};
6use crate::features::SpaceService;
7use crate::features::authz;
8use crate::metrics;
912
1013use super::{CreateSpaceCmd, SpaceAllowJoinLevel, SpacePage, SpaceView, UpdateSpaceCmd};
1114
@@ -30,6 +33,7 @@
3033 }
3134
3235 let writer = svc.db.writer();
36 let txn = writer.begin().await?;
3337 let now = OffsetDateTime::now_utc();
3438 let id = Uuid::now_v7();
3539 let active: space_entity::ActiveModel = space_entity::ActiveModel::builder()
@@ -62,8 +66,24 @@
6266 .set_created_at(now)
6367 .set_updated_at(now)
6468 .into();
65 let saved = active.insert(&writer).await?;
66 Ok(saved.into())
69 let saved = active.insert(&txn).await?;
70 // 创建者自动成为成员(status=Active),使 /user/me/spaces 能查到该空间
71 let owner_member: member_entity::ActiveModel = member_entity::ActiveModel::builder()
72 .set_space_id(id)
73 .set_user_id(caller)
74 .set_alias_name(Option::<String>::None)
75 .set_alias_avatar_url(Option::<String>::None)
76 .set_status(member_entity::MemberStatus::Active)
77 .set_invited_at(Option::<OffsetDateTime>::None)
78 .set_invited_by(Option::<Uuid>::None)
79 .set_joined_at(now)
80 .set_created_at(now)
81 .set_updated_at(Option::<OffsetDateTime>::Some(now))
82 .into();
83 owner_member.insert(&txn).await?;
84 txn.commit().await?;
85 let owner = load_user(&svc.db, caller).await?.map(UserBasicInfo::from);
86 Ok(SpaceView::from_model(saved, owner))
6787 }
6888 .await;
6989 metrics::record_outcome(metrics::FEATURE_SPACES_CREATE, &result);
@@ -86,11 +106,17 @@
86106 return Err(SpaceError::NotFound(format!("space {id}")));
87107 }
88108 if space.public {
89 return Ok(space.into());
109 let owner_id = space.owner_id;
110 let owner = load_user(&svc.db, owner_id).await?.map(UserBasicInfo::from);
111 return Ok(SpaceView::from_model(space, owner));
90112 }
91113 let uid = caller.ok_or_else(|| SpaceError::NotFound(format!("space {id}")))?;
92114 match authz::effective_permissions(&db, id, uid).await {
93 Ok(_) => Ok(space.into()),
115 Ok(_) => {
116 let owner_id = space.owner_id;
117 let owner = load_user(&svc.db, owner_id).await?.map(UserBasicInfo::from);
118 Ok(SpaceView::from_model(space, owner))
119 }
94120 Err(SpaceError::Forbidden(_)) => Err(SpaceError::NotFound(format!("space {id}"))),
95121 Err(e) => Err(e),
96122 }
@@ -169,7 +195,9 @@
169195 active.updated_at = Set(OffsetDateTime::now_utc());
170196 let saved = active.update(&txn).await?;
171197 txn.commit().await?;
172 Ok(saved.into())
198 let owner_id = saved.owner_id;
199 let owner = load_user(&svc.db, owner_id).await?.map(UserBasicInfo::from);
200 Ok(SpaceView::from_model(saved, owner))
173201 }
174202 .await;
175203 metrics::record_outcome(metrics::FEATURE_SPACES_UPDATE, &result);
@@ -223,7 +251,9 @@
223251
224252 let new_lower = new_name.to_lowercase();
225253 if space.lower_name == new_lower {
226 return Ok(space.into());
254 let owner_id = space.owner_id;
255 let owner = load_user(&svc.db, owner_id).await?.map(UserBasicInfo::from);
256 return Ok(SpaceView::from_model(space, owner));
227257 }
228258
229259 // The new name must be free both among current names (spaces) and
@@ -255,7 +285,9 @@
255285 active.updated_at = Set(now);
256286 let saved = active.update(&txn).await?;
257287 txn.commit().await?;
258 Ok(saved.into())
288 let owner_id = saved.owner_id;
289 let owner = load_user(&svc.db, owner_id).await?.map(UserBasicInfo::from);
290 Ok(SpaceView::from_model(saved, owner))
259291 }
260292 .await;
261293 metrics::record_outcome(metrics::FEATURE_SPACES_UPDATE, &result);
@@ -277,7 +309,9 @@
277309 DatabaseBackend::Postgres => {
278310 "INSERT INTO space_name_history (id, space_id, lower_name, renamed_at, created_at) VALUES ($1, $2, $3, $4, $5)"
279311 }
280 _ => "INSERT INTO space_name_history (id, space_id, lower_name, renamed_at, created_at) VALUES (?, ?, ?, ?, ?)",
312 _ => {
313 "INSERT INTO space_name_history (id, space_id, lower_name, renamed_at, created_at) VALUES (?, ?, ?, ?, ?)"
314 }
281315 };
282316 txn.execute_raw(Statement::from_sql_and_values(
283317 backend,
@@ -371,9 +405,17 @@
371405 );
372406 let items = items?;
373407 let total = total? as i64;
408 let owner_ids: Vec<Uuid> = items.iter().map(|s| s.owner_id).collect();
409 let users = load_users(&svc.db, &owner_ids).await?;
374410
375411 Ok(SpacePage {
376 items: items.into_iter().map(Into::into).collect(),
412 items: items
413 .into_iter()
414 .map(|s| {
415 let owner_id = s.owner_id;
416 SpaceView::from_model(s, users.get(&owner_id).map(UserBasicInfo::from))
417 })
418 .collect(),
377419 total,
378420 })
379421 }
@@ -426,13 +468,69 @@
426468 .expect("disable foreign keys for test");
427469 db
428470 };
429 db.execute_unprepared("CREATE TABLE IF NOT EXISTS users (uid TEXT PRIMARY KEY)")
430 .await
431 .expect("create stub users table");
471 db.execute_unprepared(
472 "CREATE TABLE IF NOT EXISTS users (
473 uid TEXT PRIMARY KEY,
474 username TEXT NOT NULL,
475 lower_name TEXT NOT NULL,
476 display_name TEXT NOT NULL,
477 avatar TEXT NOT NULL,
478 is_active BOOLEAN NOT NULL DEFAULT 0,
479 is_admin BOOLEAN NOT NULL DEFAULT 0,
480 is_restricted BOOLEAN NOT NULL DEFAULT 0,
481 prohibit_login BOOLEAN NOT NULL DEFAULT 0,
482 visibility SMALLINT NOT NULL DEFAULT 0,
483 bot BOOLEAN NOT NULL DEFAULT 0,
484 system BOOLEAN NOT NULL DEFAULT 0,
485 last_login_at TEXT,
486 last_login_ip_hash TEXT,
487 version INTEGER NOT NULL DEFAULT 0,
488 passwd_hash TEXT,
489 password_last_changed_at TEXT,
490 passwd_version INTEGER,
491 totp_encrypted_secret TEXT,
492 totp_enabled BOOLEAN NOT NULL DEFAULT 0,
493 totp_last_used_at TEXT,
494 totp_last_counter INTEGER,
495 timezone TEXT,
496 locale TEXT,
497 language TEXT,
498 created_at TEXT NOT NULL,
499 updated_at TEXT NOT NULL
500 )",
501 )
502 .await
503 .expect("create stub users table");
432504 let schema = db::conn::sea_orm::Schema::new(db::conn::sea_orm::DatabaseBackend::Sqlite);
433505 db.execute(&schema.create_table_from_entity(space_entity::Entity))
434506 .await
435507 .expect("create spaces table");
508 db.execute_unprepared(
509 "CREATE TABLE IF NOT EXISTS space_members (
510 space_id TEXT NOT NULL,
511 user_id TEXT NOT NULL,
512 alias_name TEXT,
513 alias_avatar_url TEXT,
514 status SMALLINT NOT NULL DEFAULT 0,
515 last_active_at TEXT,
516 left_at TEXT,
517 invited_at TEXT,
518 invited_by TEXT,
519 joined_at TEXT NOT NULL,
520 created_at TEXT NOT NULL,
521 updated_at TEXT,
522 message_notifications SMALLINT NOT NULL DEFAULT 0,
523 muted BOOLEAN NOT NULL DEFAULT 0,
524 mute_until TEXT,
525 mobile_push BOOLEAN NOT NULL DEFAULT 0,
526 suppress_everyone BOOLEAN NOT NULL DEFAULT 0,
527 suppress_roles BOOLEAN NOT NULL DEFAULT 0,
528 hide_muted_channels BOOLEAN NOT NULL DEFAULT 0,
529 PRIMARY KEY (space_id, user_id)
530 )",
531 )
532 .await
533 .expect("create space_members table");
436534 db.execute_unprepared(include_str!("../../../db/schema_name_history.sql"))
437535 .await
438536 .expect("create space_name_history table");
@@ -442,7 +540,12 @@
442540 Arc::new(RwLock::new(SigningKeyCache::new())),
443541 );
444542 let owner = Uuid::now_v7();
445 db.execute_unprepared(&format!("INSERT INTO users (uid) VALUES ('{owner}')"))
543 let now = time::OffsetDateTime::now_utc()
544 .format(&time::format_description::well_known::Rfc3339)
545 .unwrap();
546 db.execute_unprepared(&format!(
547 "INSERT INTO users (uid, username, lower_name, display_name, avatar, is_active, visibility, bot, system, created_at, updated_at) VALUES ('{owner}', 'owner', 'owner', 'Owner', '', 1, 0, 0, 0, '{now}', '{now}')"
548 ))
446549 .await
447550 .expect("insert stub user");
448551 let svc = SpaceService::new(Db::from_connection(db), kv, jwt);
@@ -464,7 +567,9 @@
464567 }
465568
466569 async fn create_space(svc: &SpaceService, owner: Uuid, name: &str) -> SpaceView {
467 svc.create_space(owner, create_cmd(name)).await.expect("create")
570 svc.create_space(owner, create_cmd(name))
571 .await
572 .expect("create")
468573 }
469574
470575 /// Test 1: after a rename the OLD url still resolves — the old name is
Modified lib/space/features/lookup.rs lib/space/features/lookup.rs +4 −5 View lib/space/features/lookup.rs
@@ -11,10 +11,7 @@
1111/// `spaces.lower_name`; names retired by a rename live in
1212/// `space_name_history.lower_name`, so old URLs keep resolving to the same
1313/// space.
14pub async fn lookup_space_by_name<S: SpaceDb>(
15 svc: &S,
16 lower_name: &str,
17) -> SpaceResult<Uuid> {
14pub async fn lookup_space_by_name<S: SpaceDb>(svc: &S, lower_name: &str) -> SpaceResult<Uuid> {
1815 let db = svc.db().reader();
1916 if let Some(space) = space_entity::Entity::find()
2017 .filter(space_entity::Column::LowerName.eq(lower_name))
@@ -54,5 +51,7 @@
5451 if space_id.is_empty() {
5552 return Ok(None);
5653 }
57 Uuid::parse_str(&space_id).map(Some).map_err(SpaceError::from)
54 Uuid::parse_str(&space_id)
55 .map(Some)
56 .map_err(SpaceError::from)
5857}
Modified lib/space/features/mod.rs lib/space/features/mod.rs +1 −0 View lib/space/features/mod.rs
@@ -14,6 +14,7 @@
1414pub mod members;
1515pub mod overwrites;
1616pub mod roles;
17pub mod sidebar;
1718pub mod spaces;
1819
1920/// Minimal database access surface for the space lookup / authz helpers.
Modified lib/space/handler/channels/list.rs lib/space/handler/channels/list.rs +3 −1 View lib/space/handler/channels/list.rs
@@ -26,6 +26,8 @@
2626) -> SpaceResult<AppResponseData<ChannelPage>> {
2727 let space_id = crate::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2828 let (limit, offset) = (q.limit.unwrap_or(0), q.offset.unwrap_or(0));
29 let page = svc.list_channels(claims.sub, space_id, limit, offset).await?;
29 let page = svc
30 .list_channels(claims.sub, space_id, limit, offset)
31 .await?;
3032 Ok(AppResponseData::ok(page))
3133}
Modified lib/space/handler/invites/create.rs lib/space/handler/invites/create.rs +3 −1 View lib/space/handler/invites/create.rs
@@ -19,6 +19,8 @@
1919 cmd: AppRequest<CreateInviteCmd>,
2020) -> SpaceResult<AppResponseData<SpaceInviteView>> {
2121 let space_id = crate::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
22 let view = svc.create_invite(claims.sub, space_id, cmd.into_inner()).await?;
22 let view = svc
23 .create_invite(claims.sub, space_id, cmd.into_inner())
24 .await?;
2325 Ok(AppResponseData::ok(view))
2426}
Modified lib/space/handler/invites/list.rs lib/space/handler/invites/list.rs +3 −1 View lib/space/handler/invites/list.rs
@@ -25,6 +25,8 @@
2525) -> SpaceResult<AppResponseData<SpaceInvitePage>> {
2626 let space_id = crate::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2727 let (limit, offset) = (q.limit.unwrap_or(0), q.offset.unwrap_or(0));
28 let page = svc.list_invites(claims.sub, space_id, limit, offset).await?;
28 let page = svc
29 .list_invites(claims.sub, space_id, limit, offset)
30 .await?;
2931 Ok(AppResponseData::ok(page))
3032}
Modified lib/space/handler/members/add.rs lib/space/handler/members/add.rs +3 −1 View lib/space/handler/members/add.rs
@@ -23,6 +23,8 @@
2323 cmd: AppRequest<AddMemberCmd>,
2424) -> SpaceResult<AppResponseData<SpaceMemberView>> {
2525 let space_id = crate::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
26 let view = svc.add_member(claims.sub, space_id, cmd.into_inner()).await?;
26 let view = svc
27 .add_member(claims.sub, space_id, cmd.into_inner())
28 .await?;
2729 Ok(AppResponseData::ok(view))
2830}
Modified lib/space/handler/members/list.rs lib/space/handler/members/list.rs +7 −2 View lib/space/handler/members/list.rs
@@ -25,7 +25,12 @@
2525 Query(q): Query<SpaceListQuery>,
2626) -> SpaceResult<AppResponseData<SpaceMemberPage>> {
2727 let space_id = crate::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
28 let (limit, offset) = (q.limit.unwrap_or(0), q.offset.unwrap_or(0));
29 let page = svc.list_members(claims.sub, space_id, limit, offset).await?;
28 let (limit, offset) = (
29 q.limit.unwrap_or(svc.config.default_pagination),
30 q.offset.unwrap_or(0),
31 );
32 let page = svc
33 .list_members(claims.sub, space_id, limit, offset)
34 .await?;
3035 Ok(AppResponseData::ok(page))
3136}
Modified lib/space/handler/overwrites/remove_role.rs lib/space/handler/overwrites/remove_role.rs +2 −1 View lib/space/handler/overwrites/remove_role.rs
@@ -24,6 +24,7 @@
2424 Path((space_name, cid, rid)): Path<(String, Uuid, Uuid)>,
2525) -> SpaceResult<AppResponseData<NoData>> {
2626 let space_id = crate::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
27 svc.remove_role_overwrite(claims.sub, space_id, cid, rid).await?;
27 svc.remove_role_overwrite(claims.sub, space_id, cid, rid)
28 .await?;
2829 Ok(AppResponseData::<NoData>::empty())
2930}
Modified lib/space/handler/overwrites/remove_user.rs lib/space/handler/overwrites/remove_user.rs +2 −1 View lib/space/handler/overwrites/remove_user.rs
@@ -24,6 +24,7 @@
2424 Path((space_name, cid, uid)): Path<(String, Uuid, Uuid)>,
2525) -> SpaceResult<AppResponseData<NoData>> {
2626 let space_id = crate::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
27 svc.remove_user_overwrite(claims.sub, space_id, cid, uid).await?;
27 svc.remove_user_overwrite(claims.sub, space_id, cid, uid)
28 .await?;
2829 Ok(AppResponseData::<NoData>::empty())
2930}
Modified lib/space/handler/roles/assign.rs lib/space/handler/roles/assign.rs +2 −1 View lib/space/handler/roles/assign.rs
@@ -27,6 +27,7 @@
2727 cmd: AppRequest<AssignRoleCmd>,
2828) -> SpaceResult<AppResponseData<NoData>> {
2929 let space_id = crate::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
30 svc.assign_role(claims.sub, space_id, uid, cmd.role_id).await?;
30 svc.assign_role(claims.sub, space_id, uid, cmd.role_id)
31 .await?;
3132 Ok(AppResponseData::<NoData>::empty())
3233}
Modified lib/space/handler/roles/create.rs lib/space/handler/roles/create.rs +3 −1 View lib/space/handler/roles/create.rs
@@ -25,6 +25,8 @@
2525 cmd: AppRequest<CreateRoleCmd>,
2626) -> SpaceResult<AppResponseData<RoleView>> {
2727 let space_id = crate::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
28 let view = svc.create_role(claims.sub, space_id, cmd.into_inner()).await?;
28 let view = svc
29 .create_role(claims.sub, space_id, cmd.into_inner())
30 .await?;
2931 Ok(AppResponseData::ok(view))
3032}
Modified lib/space/handler/spaces/update.rs lib/space/handler/spaces/update.rs +3 −1 View lib/space/handler/spaces/update.rs
@@ -13,6 +13,8 @@
1313 cmd: AppRequest<UpdateSpaceCmd>,
1414) -> SpaceResult<AppResponseData<SpaceView>> {
1515 let space_id = crate::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
16 let view = svc.update_space(claims.sub, space_id, cmd.into_inner()).await?;
16 let view = svc
17 .update_space(claims.sub, space_id, cmd.into_inner())
18 .await?;
1719 Ok(AppResponseData::ok(view))
1820}
Modified lib/space/handler/mod.rs lib/space/handler/mod.rs +1 −0 View lib/space/handler/mod.rs
@@ -8,6 +8,7 @@
88pub(crate) mod query;
99pub(crate) mod roles;
1010pub(crate) mod routes;
11pub(crate) mod sidebar;
1112pub(crate) mod spaces;
1213
1314pub use openapi::SpaceApiDoc;
Modified lib/space/handler/openapi.rs lib/space/handler/openapi.rs +3 −0 View lib/space/handler/openapi.rs
@@ -13,6 +13,7 @@
1313use export::space::roles::{
1414 AssignRoleCmd, CreateRoleCmd, RolePage, RoleView, UnassignRoleCmd, UpdateRoleCmd,
1515};
16use export::space::sidebar::{SidebarNode, SidebarNodeKind, SidebarTreeView};
1617use export::space::spaces::{
1718 CreateSpaceCmd, SpaceAllowJoinLevel, SpacePage, SpaceView, UpdateSpaceCmd,
1819};
@@ -56,6 +57,7 @@
5657 crate::handler::bans::unban::handler,
5758 crate::handler::channels::get::handler,
5859 crate::handler::channels::list::handler,
60 crate::handler::sidebar::get::handler,
5961 crate::handler::overwrites::set_role::handler,
6062 crate::handler::overwrites::remove_role::handler,
6163 crate::handler::overwrites::list_role::handler,
@@ -71,6 +73,7 @@
7173 SpaceInviteView, CreateInviteCmd, AcceptInviteCmd, SpaceInvitePage,
7274 SpaceBanView, CreateBanCmd, UpdateBanCmd, SpaceBanPage,
7375 ChannelView, ChannelPage,
76 SidebarTreeView, SidebarNode, SidebarNodeKind,
7477 RoleOverwriteView, SetRoleOverwriteCmd, UserOverwriteView, SetUserOverwriteCmd,
7578 ))
7679)]
Modified lib/space/handler/routes.rs lib/space/handler/routes.rs +12 −2 View lib/space/handler/routes.rs
@@ -9,7 +9,10 @@
99pub fn router(svc: SpaceService) -> Router {
1010 let public = Router::new()
1111 .route("/spaces/{space_name}", get(super::spaces::get::handler))
12 .route("/spaces/{space_name}/roles/{rid}", get(super::roles::get::handler))
12 .route(
13 "/spaces/{space_name}/roles/{rid}",
14 get(super::roles::get::handler),
15 )
1316 .layer(from_fn_with_state(
1417 svc.clone(),
1518 inject_jwt_ctx::<SpaceService>,
@@ -70,8 +73,15 @@
7073 .patch(super::bans::update::handler)
7174 .delete(super::bans::unban::handler),
7275 )
73 .route("/spaces/{space_name}/channels", get(super::channels::list::handler))
7476 .route(
77 "/spaces/{space_name}/channels",
78 get(super::channels::list::handler),
79 )
80 .route(
81 "/spaces/{space_name}/sidebar",
82 get(super::sidebar::get::handler),
83 )
84 .route(
7585 "/spaces/{space_name}/channels/{cid}",
7686 get(super::channels::get::handler),
7787 )