feat(api): phase5a UserDto unification, uid renames, embeds (invites/presence/search/whitelists/commit-status/reminders/room owner), storage URL resolution
ZhenYi committed
Modified lib/auth/src/services/login.rs lib/auth/src/services/login.rs +11 −13 View lib/auth/src/services/login.rs
@@ -48,17 +48,6 @@
4848 pub ip_hash: Option<String>,
4949}
5050
51impl From<&user::Model> for UserPublic {
52 fn from(user: &user::Model) -> Self {
53 Self {
54 user_id: user.uid,
55 username: user.username.clone(),
56 display_name: user.display_name.clone(),
57 avatar_url: user.avatar_url.clone(),
58 }
59 }
60}
61
6251impl AuthService {
6352 pub async fn login(
6453 &self,
@@ -120,7 +109,7 @@
120109 };
121110 session.set(TWO_FA_KEY, &challenge);
122111 return Ok(LoginResp {
123 user: UserPublic::from(&user),
112 user: user_dto(&user),
124113 needs_2fa: true,
125114 challenge: Some(challenge.challenge.clone()),
126115 });
@@ -294,7 +283,7 @@
294283 let _ = active.update(db.write()).await;
295284
296285 Ok(LoginResp {
297 user: UserPublic::from(user),
286 user: user_dto(&user),
298287 needs_2fa: false,
299288 challenge: None,
300289 })
@@ -378,3 +367,12 @@
378367 }
379368 }
380369}
370
371fn user_dto(u: &user::Model) -> UserPublic {
372 UserPublic {
373 user_id: u.uid,
374 username: u.username.clone(),
375 display_name: u.display_name.clone(),
376 avatar_url: space::services::spaces::resolve_user_avatar_url(u.uid, &u.avatar_url),
377 }
378}
Modified lib/auth/src/services/notifications.rs lib/auth/src/services/notifications.rs +2 −2 View lib/auth/src/services/notifications.rs
@@ -19,7 +19,7 @@
1919 pub title: String,
2020 pub body: Option<String>,
2121 pub data: Option<JsonValue>,
22 pub actor_uid: Option<Uuid>,
22 pub actor_id: Option<Uuid>,
2323 pub read_at: Option<OffsetDateTime>,
2424 pub created_at: OffsetDateTime,
2525}
@@ -56,7 +56,7 @@
5656 title: row.title,
5757 body: row.body,
5858 data: row.data_json,
59 actor_uid: row.actor_uid,
59 actor_id: row.actor_uid,
6060 read_at: row.read_at,
6161 created_at: row.created_at,
6262 })
Modified lib/auth/src/services/profile.rs lib/auth/src/services/profile.rs +13 −6 View lib/auth/src/services/profile.rs
@@ -7,6 +7,7 @@
77
88use super::AuthService;
99use crate::errors::AuthError;
10use crate::share::types::UserPublic;
1011use crate::share::validate::validate_age;
1112
1213#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
@@ -77,7 +78,10 @@
7778 user_id: user.uid,
7879 username: user.username,
7980 display_name: user.display_name,
80 avatar_url: user.avatar_url,
81 avatar_url: space::services::spaces::resolve_user_avatar_url(
82 user.uid,
83 &user.avatar_url,
84 ),
8185 is_active: user.is_active,
8286 is_restricted: user.is_restricted,
8387 prohibit_login: user.prohibit_login,
@@ -221,7 +225,7 @@
221225 &self,
222226 uid: Uuid,
223227 new_username: &str,
224 ) -> Result<crate::share::types::UserPublic, AuthError> {
228 ) -> Result<UserPublic, AuthError> {
225229 let username = crate::share::validate::validate_username(new_username.trim())?;
226230 let lower = username.to_lowercase();
227231 let db = self.db();
@@ -245,11 +249,14 @@
245249 active.updated_at = Set(OffsetDateTime::now_utc());
246250 active.update(db.write()).await?;
247251 }
248 Ok(crate::share::types::UserPublic {
252 Ok(UserPublic {
249253 user_id: uid,
250 username,
251 display_name: user.display_name,
252 avatar_url: user.avatar_url,
254 username: username.clone(),
255 display_name: user.display_name.clone(),
256 avatar_url: space::services::spaces::resolve_user_avatar_url(
257 uid,
258 &user.avatar_url,
259 ),
253260 })
254261 }
255262}
Modified lib/auth/src/services/reports.rs lib/auth/src/services/reports.rs +6 −6 View lib/auth/src/services/reports.rs
@@ -10,7 +10,7 @@
1010
1111#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
1212pub struct CreateReportCmd {
13 pub reported_uid: Uuid,
13 pub reported_id: Uuid,
1414 pub reason: String,
1515 pub evidence: Option<String>,
1616}
@@ -18,7 +18,7 @@
1818#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
1919pub struct ReportDto {
2020 pub id: Uuid,
21 pub reported_uid: Uuid,
21 pub reported_id: Uuid,
2222 pub reason: String,
2323 pub evidence: Option<String>,
2424 pub status: ReportStatus,
@@ -31,7 +31,7 @@
3131 reporter_uid: Uuid,
3232 cmd: CreateReportCmd,
3333 ) -> Result<ReportDto, AuthError> {
34 if reporter_uid == cmd.reported_uid {
34 if reporter_uid == cmd.reported_id {
3535 return Err(AuthError::BadRequest("cannot report yourself".to_string()));
3636 }
3737 let reason = cmd.reason.trim().to_string();
@@ -41,7 +41,7 @@
4141 ));
4242 }
4343 let db = self.db();
44 let reported = models::users::user::Entity::find_by_id(cmd.reported_uid)
44 let reported = models::users::user::Entity::find_by_id(cmd.reported_id)
4545 .one(db.read())
4646 .await?;
4747 if reported.is_none() {
@@ -51,7 +51,7 @@
5151 let row = user_reports::ActiveModel {
5252 id: Set(Uuid::now_v7()),
5353 reporter_uid: Set(Some(reporter_uid)),
54 reported_uid: Set(cmd.reported_uid),
54 reported_uid: Set(cmd.reported_id),
5555 reason: Set(reason),
5656 evidence: Set(cmd.evidence),
5757 status: Set(ReportStatus::Open),
@@ -63,7 +63,7 @@
6363 let inserted = row.insert(db.write()).await?;
6464 Ok(ReportDto {
6565 id: inserted.id,
66 reported_uid: inserted.reported_uid,
66 reported_id: inserted.reported_uid,
6767 reason: inserted.reason,
6868 evidence: inserted.evidence,
6969 status: inserted.status,
Modified lib/auth/src/services/users.rs lib/auth/src/services/users.rs +4 −1 View lib/auth/src/services/users.rs
@@ -21,7 +21,10 @@
2121 user_id: user.uid,
2222 username: user.username,
2323 display_name: user.display_name,
24 avatar_url: user.avatar_url,
24 avatar_url: space::services::spaces::resolve_user_avatar_url(
25 user.uid,
26 &user.avatar_url,
27 ),
2528 })
2629 }
2730
Modified lib/auth/Cargo.toml lib/auth/Cargo.toml +1 −0 View lib/auth/Cargo.toml
@@ -9,6 +9,7 @@
99session = { workspace = true }
1010export = { workspace = true }
1111models = { workspace = true }
12space = { workspace = true }
1213pow-solver = { workspace = true }
1314axum = { workspace = true }
1415sea-orm = { workspace = true }
Modified lib/chat/src/services/bookmarks.rs lib/chat/src/services/bookmarks.rs +2 −2 View lib/chat/src/services/bookmarks.rs
@@ -21,7 +21,7 @@
2121 pub url: Option<String>,
2222 pub emoji: Option<String>,
2323 pub position: i64,
24 pub created_by: Uuid,
24 pub created_by_id: Uuid,
2525 pub created_at: OffsetDateTime,
2626}
2727
@@ -55,7 +55,7 @@
5555 url: b.url,
5656 emoji: b.emoji,
5757 position: b.position,
58 created_by: b.user_id,
58 created_by_id: b.user_id,
5959 created_at: b.created_at,
6060 }
6161 }
Modified lib/chat/src/services/dms.rs lib/chat/src/services/dms.rs +4 −4 View lib/chat/src/services/dms.rs
@@ -5,7 +5,7 @@
55use models::users::user;
66use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
77use serde::{Deserialize, Serialize};
8use space::services::spaces::UserBriefDto;
8use space::services::spaces::UserDto;
99use time::OffsetDateTime;
1010use utoipa::ToSchema;
1111use uuid::Uuid;
@@ -19,7 +19,7 @@
1919 pub id: Uuid,
2020 pub kind: RoomKind,
2121 pub name: String,
22 pub peer: Option<UserBriefDto>,
22 pub peer: Option<UserDto>,
2323 pub last_message_id: Option<Uuid>,
2424 pub last_read_seq: i64,
2525 pub tip_seq: i64,
@@ -168,7 +168,7 @@
168168 .await?;
169169 let peer_uids: Vec<Uuid> = peer_rows.iter().map(|m| m.user_id).collect();
170170 let user_map = self.load_user_briefs(&peer_uids).await?;
171 let mut peer_by_room: std::collections::HashMap<Uuid, Option<UserBriefDto>> =
171 let mut peer_by_room: std::collections::HashMap<Uuid, Option<UserDto>> =
172172 std::collections::HashMap::new();
173173 for m in peer_rows {
174174 peer_by_room
@@ -216,7 +216,7 @@
216216 &self,
217217 caller: Uuid,
218218 room: rooms::Model,
219 peer: Option<UserBriefDto>,
219 peer: Option<UserDto>,
220220 ) -> Result<DmDto, ChatError> {
221221 let tip = room_seq::Entity::find_by_id(room.id)
222222 .one(self.db().read())
Modified lib/chat/src/services/messages.rs lib/chat/src/services/messages.rs +5 −5 View lib/chat/src/services/messages.rs
@@ -12,7 +12,7 @@
1212};
1313use serde::{Deserialize, Serialize};
1414use serde_json::json;
15use space::services::spaces::UserBriefDto;
15use space::services::spaces::UserDto;
1616use time::OffsetDateTime;
1717use utoipa::{IntoParams, ToSchema};
1818use uuid::Uuid;
@@ -37,7 +37,7 @@
3737pub struct MessageDto {
3838 pub id: Uuid,
3939 pub room_id: Uuid,
40 pub author: Option<UserBriefDto>,
40 pub author: Option<UserDto>,
4141 pub author_id: Uuid,
4242 pub author_type: AuthorType,
4343 pub webhook_id: Option<Uuid>,
@@ -52,7 +52,7 @@
5252 pub attachments: Vec<AttachmentDto>,
5353 pub has_thread: bool,
5454 pub pinned: bool,
55 pub pinned_by: Option<Uuid>,
55 pub pinned_by_id: Option<Uuid>,
5656 pub pinned_at: Option<OffsetDateTime>,
5757 pub edited_at: Option<OffsetDateTime>,
5858 pub deleted_at: Option<OffsetDateTime>,
@@ -143,7 +143,7 @@
143143pub struct MessageEditDto {
144144 pub id: Uuid,
145145 pub message_id: Uuid,
146 pub editor: Option<UserBriefDto>,
146 pub editor: Option<UserDto>,
147147 pub content: String,
148148 pub created_at: OffsetDateTime,
149149}
@@ -215,7 +215,7 @@
215215 attachments: attachments_by_msg.remove(&m.id).unwrap_or_default(),
216216 has_thread: m.flags & messages::FLAG_HAS_THREAD != 0,
217217 pinned: m.pinned,
218 pinned_by: m.pinned_by,
218 pinned_by_id: m.pinned_by,
219219 pinned_at: m.pinned_at,
220220 edited_at: m.edited_at,
221221 deleted_at: m.deleted_at,
Modified lib/chat/src/services/presence.rs lib/chat/src/services/presence.rs +18 −0 View lib/chat/src/services/presence.rs
@@ -16,6 +16,7 @@
1616#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1717pub struct PresenceDto {
1818 pub user_id: Uuid,
19 pub user: Option<space::services::spaces::UserDto>,
1920 pub status: PresenceStatus,
2021 pub activities: serde_json::Value,
2122 pub last_seen_at: Option<OffsetDateTime>,
@@ -100,6 +101,7 @@
100101
101102 Ok(PresenceDto {
102103 user_id: caller,
104 user: None,
103105 status: cmd.status,
104106 activities,
105107 last_seen_at: Some(now),
@@ -108,6 +110,13 @@
108110 }
109111
110112 pub async fn get_presence(&self, uid: Uuid) -> Result<PresenceDto, ChatError> {
113 let mut dto = self.get_presence_raw(uid).await?;
114 let map = self.load_user_briefs(&[uid]).await?;
115 dto.user = map.get(&uid).cloned();
116 Ok(dto)
117 }
118
119 async fn get_presence_raw(&self, uid: Uuid) -> Result<PresenceDto, ChatError> {
111120 let mut conn = self.state.redis.connection_clone();
112121 let key = presence_key(uid);
113122 let value: Result<Option<String>, redis::RedisError> = match &mut conn {
@@ -119,6 +128,7 @@
119128 {
120129 return Ok(PresenceDto {
121130 user_id: uid,
131 user: None,
122132 status: payload.status,
123133 activities: payload.activities,
124134 last_seen_at: Some(
@@ -134,6 +144,7 @@
134144 match row {
135145 Some(row) => Ok(PresenceDto {
136146 user_id: uid,
147 user: None,
137148 status: PresenceStatus::Offline,
138149 activities: serde_json::json!([]),
139150 last_seen_at: row.last_seen_at,
@@ -141,6 +152,7 @@
141152 }),
142153 None => Ok(PresenceDto {
143154 user_id: uid,
155 user: None,
144156 status: PresenceStatus::Offline,
145157 activities: serde_json::json!([]),
146158 last_seen_at: None,
@@ -252,6 +264,7 @@
252264 if let Some(payload) = by_uid.get(uid) {
253265 result.push(PresenceDto {
254266 user_id: *uid,
267 user: None,
255268 status: payload.status,
256269 activities: payload.activities.clone(),
257270 last_seen_at: Some(
@@ -265,12 +278,17 @@
265278 let row = rows.iter().find(|r| r.user_id == *uid);
266279 result.push(PresenceDto {
267280 user_id: *uid,
281 user: None,
268282 status: PresenceStatus::Offline,
269283 activities: serde_json::json!([]),
270284 last_seen_at: row.and_then(|r| r.last_seen_at),
271285 online: false,
272286 });
273287 }
288 let map = self.load_user_briefs(uids).await?;
289 for dto in result.iter_mut() {
290 dto.user = map.get(&dto.user_id).cloned();
291 }
274292 return Ok(result);
275293 }
276294 Ok(Vec::new())
Modified lib/chat/src/services/reactions.rs lib/chat/src/services/reactions.rs +1 −1 View lib/chat/src/services/reactions.rs
@@ -21,7 +21,7 @@
2121#[derive(Debug, Clone, Serialize, ToSchema)]
2222pub struct ReactionUserDto {
2323 pub emoji: String,
24 pub user: space::services::spaces::UserBriefDto,
24 pub user: space::services::spaces::UserDto,
2525 pub created_at: OffsetDateTime,
2626}
2727
Modified lib/chat/src/services/reminders.rs lib/chat/src/services/reminders.rs +26 −1 View lib/chat/src/services/reminders.rs
@@ -20,6 +20,7 @@
2020 pub id: Uuid,
2121 pub room_id: Uuid,
2222 pub message_id: Uuid,
23 pub message_preview: Option<String>,
2324 pub remind_at: OffsetDateTime,
2425 pub note: Option<String>,
2526 pub status: ReminderStatus,
@@ -49,6 +50,7 @@
4950 message_id: r.message_id,
5051 remind_at: r.remind_at,
5152 note: r.note,
53 message_preview: None,
5254 status: r.status,
5355 created_at: r.created_at,
5456 }
@@ -97,7 +99,30 @@
9799 .order_by_asc(message_reminders::Column::RemindAt)
98100 .all(self.db().read())
99101 .await?;
100 Ok(rows.into_iter().map(ReminderDto::from).collect())
102 let mut items: Vec<ReminderDto> = rows.iter().map(|r| ReminderDto::from(r.clone())).collect();
103 let msg_ids: Vec<Uuid> = rows.iter().map(|r| r.message_id).collect();
104 if !msg_ids.is_empty() {
105 use models::chat::messages;
106 use sea_orm::{ColumnTrait as _, EntityTrait as _, QueryFilter as _};
107 let msgs = messages::Entity::find()
108 .filter(messages::Column::Id.is_in(msg_ids))
109 .all(self.db().read())
110 .await?;
111 let previews: std::collections::HashMap<Uuid, String> = msgs
112 .into_iter()
113 .map(|m| {
114 let mut p: String = m.content.chars().take(120).collect();
115 if p.ends_with(char::is_whitespace) {
116 p = p.trim_end().to_string();
117 }
118 (m.id, p)
119 })
120 .collect();
121 for item in items.iter_mut() {
122 item.message_preview = previews.get(&item.message_id).cloned();
123 }
124 }
125 Ok(items)
101126 }
102127
103128 pub async fn update_reminder(
Modified lib/chat/src/services/room_admin.rs lib/chat/src/services/room_admin.rs +3 −3 View lib/chat/src/services/room_admin.rs
@@ -3,7 +3,7 @@
33use models::room::rooms;
44use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, Set};
55use serde::{Deserialize, Serialize};
6use space::services::spaces::UserBriefDto;
6use space::services::spaces::UserDto;
77use time::OffsetDateTime;
88use utoipa::ToSchema;
99use uuid::Uuid;
@@ -17,9 +17,9 @@
1717#[derive(Debug, Clone, Serialize, ToSchema)]
1818pub struct RoomBanDto {
1919 pub room_id: Uuid,
20 pub user: Option<UserBriefDto>,
20 pub user: Option<UserDto>,
2121 pub reason: Option<String>,
22 pub banned_by: Option<UserBriefDto>,
22 pub banned_by: Option<UserDto>,
2323 pub expires_at: Option<OffsetDateTime>,
2424 pub created_at: OffsetDateTime,
2525}
Modified lib/chat/src/services/rooms.rs lib/chat/src/services/rooms.rs +53 −10 View lib/chat/src/services/rooms.rs
@@ -14,6 +14,7 @@
1414};
1515use serde::{Deserialize, Serialize};
1616use space::share::authz::{MANAGE_INVITES, effective_permissions};
17use space::services::spaces::UserDto;
1718use time::OffsetDateTime;
1819use utoipa::{IntoParams, ToSchema};
1920use uuid::Uuid;
@@ -32,6 +33,8 @@
3233pub struct RoomBriefDto {
3334 pub id: Uuid,
3435 pub space_id: Option<Uuid>,
36 pub owner_id: Uuid,
37 pub owner: Option<UserDto>,
3538 pub kind: RoomKind,
3639 pub name: String,
3740 pub topic: Option<String>,
@@ -78,7 +81,7 @@
7881
7982#[derive(Debug, Clone, Serialize, ToSchema)]
8083pub struct RoomMemberDto {
81 pub user: space::services::spaces::UserBriefDto,
84 pub user: space::services::spaces::UserDto,
8285 pub role: RoomMemberRole,
8386 pub nick: Option<String>,
8487 pub joined_at: OffsetDateTime,
@@ -93,7 +96,7 @@
9396#[derive(Debug, Clone, Deserialize, ToSchema)]
9497pub struct CreateRoomInviteCmd {
9598 #[serde(default)]
96 pub invitee_user_id: Option<Uuid>,
99 pub invitee_id: Option<Uuid>,
97100 #[serde(default)]
98101 pub expires_secs: Option<i64>,
99102}
@@ -103,7 +106,9 @@
103106 pub id: Uuid,
104107 pub room_id: Uuid,
105108 pub inviter_id: Uuid,
106 pub invitee_user_id: Option<Uuid>,
109 pub inviter: Option<UserDto>,
110 pub invitee_id: Option<Uuid>,
111 pub invitee: Option<UserDto>,
107112 pub accept_code: String,
108113 pub created_at: OffsetDateTime,
109114 pub expires_at: Option<OffsetDateTime>,
@@ -114,7 +119,9 @@
114119 pub id: Uuid,
115120 pub room_id: Uuid,
116121 pub inviter_id: Uuid,
117 pub invitee_user_id: Option<Uuid>,
122 pub inviter: Option<UserDto>,
123 pub invitee_id: Option<Uuid>,
124 pub invitee: Option<UserDto>,
118125 pub created_at: OffsetDateTime,
119126 pub expires_at: Option<OffsetDateTime>,
120127 pub accepted_at: Option<OffsetDateTime>,
@@ -212,6 +219,9 @@
212219 .await?;
213220 let count_map: std::collections::HashMap<Uuid, i64> = member_counts.into_iter().collect();
214221
222 let owner_ids: Vec<Uuid> =
223 rooms.iter().map(|r| r.owner_id).collect::<Vec<_>>().into_iter().collect();
224 let owner_map = self.load_user_briefs(&owner_ids).await?;
215225 let mut result = Vec::new();
216226 for room in rooms {
217227 if room.is_private && !member_of.contains(&room.id) && room.owner_id != caller {
@@ -230,6 +240,8 @@
230240 let setting = settings_map.get(&room.id);
231241 result.push(RoomBriefDto {
232242 id: room.id,
243 owner_id: room.owner_id,
244 owner: owner_map.get(&room.owner_id).cloned(),
233245 space_id: room.space_id,
234246 kind: room.kind,
235247 name: room.name,
@@ -269,8 +281,11 @@
269281 .one(self.db().read())
270282 .await?;
271283 let member_count = self.room_member_count(room.id).await? as i64;
284 let owner_map = self.load_user_briefs(&[room.owner_id]).await?;
272285 Ok(RoomBriefDto {
273286 id: room.id,
287 owner_id: room.owner_id,
288 owner: owner_map.get(&room.owner_id).cloned(),
274289 space_id: room.space_id,
275290 kind: room.kind,
276291 name: room.name,
@@ -719,7 +734,7 @@
719734 id: Set(id),
720735 room_id: Set(room_id),
721736 inviter_id: Set(caller),
722 invitee_user_id: Set(cmd.invitee_user_id),
737 invitee_user_id: Set(cmd.invitee_id),
723738 invitee_email: Set(None),
724739 accept_code: Set(accept_code.clone()),
725740 created_at: Set(now),
@@ -729,7 +744,7 @@
729744 }
730745 .insert(self.db().write())
731746 .await?;
732 if let Some(invitee) = cmd.invitee_user_id {
747 if let Some(invitee) = cmd.invitee_id {
733748 let data = serde_json::json!({"room_id": room_id, "invite_id": id});
734749 notify_user(
735750 self.db().write(),
@@ -742,11 +757,20 @@
742757 )
743758 .await?;
744759 }
760 let mut uids = vec![row.inviter_id];
761 if let Some(invitee) = row.invitee_user_id {
762 uids.push(invitee);
763 }
764 let user_map = self.load_user_briefs(&uids).await?;
745765 Ok(RoomInviteDto {
746766 id: row.id,
747767 room_id: row.room_id,
748768 inviter_id: row.inviter_id,
749 invitee_user_id: row.invitee_user_id,
769 inviter: user_map.get(&row.inviter_id).cloned(),
770 invitee_id: row.invitee_user_id,
771 invitee: row
772 .invitee_user_id
773 .and_then(|i| user_map.get(&i).cloned()),
750774 accept_code,
751775 created_at: row.created_at,
752776 expires_at: row.expires_at,
@@ -764,19 +788,38 @@
764788 .order_by_desc(room_invites::Column::CreatedAt)
765789 .all(self.db().read())
766790 .await?;
767 Ok(rows
791 let mut items: Vec<RoomInviteListItemDto> = rows
768792 .into_iter()
769793 .map(|r| RoomInviteListItemDto {
770794 id: r.id,
771795 room_id: r.room_id,
772796 inviter_id: r.inviter_id,
773 invitee_user_id: r.invitee_user_id,
797 inviter: None,
798 invitee_id: r.invitee_user_id,
799 invitee: None,
774800 created_at: r.created_at,
775801 expires_at: r.expires_at,
776802 accepted_at: r.accepted_at,
777803 revoked_at: r.revoked_at,
778804 })
779 .collect())
805 .collect();
806 let mut uids: Vec<Uuid> = items.iter().flat_map(|i| {
807 let mut v = vec![i.inviter_id];
808 if let Some(id) = i.invitee_id {
809 v.push(id);
810 }
811 v
812 }).collect();
813 uids.sort();
814 uids.dedup();
815 if !uids.is_empty() {
816 let user_map = self.load_user_briefs(&uids).await?;
817 for item in items.iter_mut() {
818 item.inviter = user_map.get(&item.inviter_id).cloned();
819 item.invitee = item.invitee_id.and_then(|i| user_map.get(&i).cloned());
820 }
821 }
822 Ok(items)
780823 }
781824
782825 pub async fn revoke_room_invite(
Modified lib/chat/src/services/search.rs lib/chat/src/services/search.rs +4 −0 View lib/chat/src/services/search.rs
@@ -13,6 +13,7 @@
1313 pub message_id: Uuid,
1414 pub room_id: Uuid,
1515 pub author_id: Uuid,
16 pub author: Option<space::services::spaces::UserDto>,
1617 pub seq: i64,
1718 pub content: String,
1819 pub created_at: OffsetDateTime,
@@ -128,9 +129,12 @@
128129 .all(self.db().read())
129130 .await?;
130131
132 let author_ids: Vec<Uuid> = rows.iter().map(|r| r.author_id).collect();
133 let author_map = self.load_user_briefs(&author_ids).await?;
131134 let items = rows
132135 .into_iter()
133136 .map(|r| SearchHitDto {
137 author: author_map.get(&r.author_id).cloned(),
134138 message_id: r.message_id,
135139 room_id: r.room_id,
136140 author_id: r.author_id,
Modified lib/chat/src/services/service.rs lib/chat/src/services/service.rs +3 −3 View lib/chat/src/services/service.rs
@@ -12,7 +12,7 @@
1212use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
1313use session::SessionError;
1414use session::share::guard::{SessionGuard, SessionWithUser};
15use space::services::spaces::UserBriefDto;
15use space::services::spaces::UserDto;
1616use uuid::Uuid;
1717
1818use crate::errors::ChatError;
@@ -52,7 +52,7 @@
5252 pub async fn load_user_briefs(
5353 &self,
5454 uids: &[Uuid],
55 ) -> Result<HashMap<Uuid, UserBriefDto>, ChatError> {
55 ) -> Result<HashMap<Uuid, UserDto>, ChatError> {
5656 if uids.is_empty() {
5757 return Ok(HashMap::new());
5858 }
@@ -65,7 +65,7 @@
6565 .await?;
6666 let map = users
6767 .into_iter()
68 .map(|u| (u.uid, UserBriefDto::from(u)))
68 .map(|u| (u.uid, UserDto::from(u)))
6969 .collect();
7070 Ok(map)
7171 }
Modified lib/chat/src/services/threads.rs lib/chat/src/services/threads.rs +2 −2 View lib/chat/src/services/threads.rs
@@ -8,7 +8,7 @@
88 QueryFilter, QueryOrder, QuerySelect, Set, TransactionTrait,
99};
1010use serde::{Deserialize, Serialize};
11use space::services::spaces::UserBriefDto;
11use space::services::spaces::UserDto;
1212use time::OffsetDateTime;
1313use utoipa::{IntoParams, ToSchema};
1414use uuid::Uuid;
@@ -24,7 +24,7 @@
2424 pub id: Uuid,
2525 pub room_id: Uuid,
2626 pub root_message_id: Uuid,
27 pub owner: Option<UserBriefDto>,
27 pub owner: Option<UserDto>,
2828 pub owner_id: Uuid,
2929 pub name: Option<String>,
3030 pub reply_count: i64,
Modified lib/chat/src/openapi.rs lib/chat/src/openapi.rs +1 −1 View lib/chat/src/openapi.rs
@@ -88,7 +88,7 @@
8888 components(schemas(
8989 AppResponseData<export::rest::NoData>,
9090 export::rest::NoData,
91 space::services::spaces::UserBriefDto,
91 space::services::spaces::UserDto,
9292 models::chat::messages::AuthorType,
9393 models::chat::messages::ChatMessageType,
9494 models::chat::attachments::AttachmentStatus,
Modified lib/git/src/service/commits.rs lib/git/src/service/commits.rs +9 −1 View lib/git/src/service/commits.rs
@@ -130,6 +130,7 @@
130130 pub description: Option<String>,
131131 pub target_url: Option<String>,
132132 pub creator_id: Uuid,
133 pub creator: Option<space::services::spaces::UserDto>,
133134 pub created_at: OffsetDateTime,
134135 pub updated_at: Option<OffsetDateTime>,
135136}
@@ -142,6 +143,7 @@
142143 description: m.description,
143144 target_url: m.target_url,
144145 creator_id: m.creator_id,
146 creator: None,
145147 created_at: m.created_at,
146148 updated_at: m.updated_at,
147149 }
@@ -450,6 +452,12 @@
450452 .order_by_asc(git_commit_status::Column::CreatedAt)
451453 .all(self.db_read())
452454 .await?;
453 Ok(rows.into_iter().map(CommitStatusDto::from).collect())
455 let mut out: Vec<CommitStatusDto> = rows.into_iter().map(CommitStatusDto::from).collect();
456 let uids: Vec<Uuid> = out.iter().map(|c| c.creator_id).collect();
457 let map = self.user_dto_map(&uids).await?;
458 for c in out.iter_mut() {
459 c.creator = map.get(&c.creator_id).cloned();
460 }
461 Ok(out)
454462 }
455463}
Modified lib/git/src/service/whitelists.rs lib/git/src/service/whitelists.rs +72 −6 View lib/git/src/service/whitelists.rs
@@ -17,6 +17,7 @@
1717 pub policy_id: Uuid,
1818 pub kind: WhitelistKind,
1919 pub user_id: Option<Uuid>,
20 pub user: Option<space::services::spaces::UserDto>,
2021 pub group_id: Option<Uuid>,
2122 pub created_at: OffsetDateTime,
2223}
@@ -28,6 +29,7 @@
2829 policy_id: m.policy_id,
2930 kind: m.kind,
3031 user_id: m.user_id,
32 user: None,
3133 group_id: m.group_id,
3234 created_at: OffsetDateTime::now_utc(),
3335 }
@@ -45,6 +47,7 @@
4547pub struct TagWhitelistDto {
4648 pub policy_id: Uuid,
4749 pub user_id: Uuid,
50 pub user: Option<space::services::spaces::UserDto>,
4851}
4952
5053impl From<repo_tag_protection_whitelists::Model> for TagWhitelistDto {
@@ -52,6 +55,7 @@
5255 Self {
5356 policy_id: m.policy_id,
5457 user_id: m.user_id,
58 user: None,
5559 }
5660 }
5761}
@@ -62,6 +66,39 @@
6266}
6367
6468impl GitService {
69 async fn embed_whitelist_users(
70 &self,
71 mut rows: Vec<BranchWhitelistDto>,
72 ) -> Result<Vec<BranchWhitelistDto>, GitError> {
73 let uids: Vec<Uuid> = rows.iter().filter_map(|r| r.user_id).collect();
74 if !uids.is_empty() {
75 let map = self.user_dto_map(&uids).await?;
76 for row in rows.iter_mut() {
77 row.user = row.user_id.and_then(|u| map.get(&u).cloned());
78 }
79 }
80 Ok(rows)
81 }
82
83 pub(crate) async fn user_dto_map(
84 &self,
85 uids: &[Uuid],
86 ) -> Result<std::collections::HashMap<Uuid, space::services::spaces::UserDto>, GitError> {
87 use models::users::user;
88 let mut map = std::collections::HashMap::new();
89 if uids.is_empty() {
90 return Ok(map);
91 }
92 let rows = user::Entity::find()
93 .filter(user::Column::Uid.is_in(uids.to_vec()))
94 .all(self.db_read())
95 .await?;
96 for u in rows {
97 map.insert(u.uid, space::services::spaces::UserDto::from(u));
98 }
99 Ok(map)
100 }
101
65102 async fn ensure_branch_policy(
66103 &self,
67104 policy_id: Uuid,
@@ -105,7 +142,12 @@
105142 .one(self.db_read())
106143 .await?;
107144 if let Some(row) = existing {
108 return Ok(row.into());
145 return Ok(self
146 .embed_whitelist_users(vec![row.into()])
147 .await?
148 .into_iter()
149 .next()
150 .unwrap());
109151 }
110152 let am = repo_branch_protection_whitelists::ActiveModel {
111153 id: Set(Uuid::now_v7()),
@@ -115,7 +157,12 @@
115157 group_id: Set(cmd.group_id),
116158 };
117159 let saved = am.insert(self.db_write()).await?;
118 Ok(saved.into())
160 Ok(self
161 .embed_whitelist_users(vec![saved.into()])
162 .await?
163 .into_iter()
164 .next()
165 .unwrap())
119166 }
120167
121168 pub async fn remove_branch_whitelist(
@@ -148,7 +195,8 @@
148195 .filter(repo_branch_protection_whitelists::Column::PolicyId.eq(policy_id))
149196 .all(self.db_read())
150197 .await?;
151 Ok(rows.into_iter().map(Into::into).collect())
198 self.embed_whitelist_users(rows.into_iter().map(Into::into).collect())
199 .await
152200 }
153201
154202 pub async fn add_tag_whitelist(
@@ -165,7 +213,13 @@
165213 .one(self.db_read())
166214 .await?;
167215 if let Some(row) = existing {
168 return Ok(row.into());
216 let mut dto: TagWhitelistDto = row.into();
217 dto.user = self
218 .user_dto_map(&[dto.user_id])
219 .await?
220 .get(&dto.user_id)
221 .cloned();
222 return Ok(dto);
169223 }
170224 let am = repo_tag_protection_whitelists::ActiveModel {
171225 policy_id: Set(policy_id),
@@ -175,7 +229,13 @@
175229 Box::pin(async move { am.insert(txn).await.map_err(GitError::from) })
176230 })
177231 .await?;
178 Ok(saved.into())
232 let mut dto: TagWhitelistDto = saved.into();
233 dto.user = self
234 .user_dto_map(&[dto.user_id])
235 .await?
236 .get(&dto.user_id)
237 .cloned();
238 Ok(dto)
179239 }
180240
181241 pub async fn remove_tag_whitelist(
@@ -207,6 +267,12 @@
207267 .filter(repo_tag_protection_whitelists::Column::PolicyId.eq(policy_id))
208268 .all(self.db_read())
209269 .await?;
210 Ok(rows.into_iter().map(Into::into).collect())
270 let mut out: Vec<TagWhitelistDto> = rows.into_iter().map(Into::into).collect();
271 let uids: Vec<Uuid> = out.iter().map(|t| t.user_id).collect();
272 let map = self.user_dto_map(&uids).await?;
273 for t in out.iter_mut() {
274 t.user = map.get(&t.user_id).cloned();
275 }
276 Ok(out)
211277 }
212278}
Modified lib/git/Cargo.toml lib/git/Cargo.toml +1 −0 View lib/git/Cargo.toml
@@ -8,6 +8,7 @@
88export = { workspace = true }
99config = { workspace = true }
1010models = { workspace = true }
11space = { workspace = true }
1112cache = { workspace = true }
1213serde = { workspace = true }
1314serde_json = { workspace = true }
Modified lib/issues/src/services/comments.rs lib/issues/src/services/comments.rs +2 −2 View lib/issues/src/services/comments.rs
@@ -9,7 +9,7 @@
99 QuerySelect, Set, TransactionTrait,
1010};
1111use serde::{Deserialize, Serialize};
12use space::services::spaces::UserBriefDto;
12use space::services::spaces::UserDto;
1313use time::OffsetDateTime;
1414use utoipa::{IntoParams, ToSchema};
1515use uuid::Uuid;
@@ -24,7 +24,7 @@
2424 pub id: Uuid,
2525 pub issue_id: Option<Uuid>,
2626 pub parent_id: Option<Uuid>,
27 pub poster: Option<UserBriefDto>,
27 pub poster: Option<UserDto>,
2828 pub content: String,
2929 pub created_at: OffsetDateTime,
3030 pub updated_at: Option<OffsetDateTime>,
Modified lib/issues/src/services/content_history.rs lib/issues/src/services/content_history.rs +2 −2 View lib/issues/src/services/content_history.rs
@@ -1,7 +1,7 @@
11use models::issue::issue_content_history;
22use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
33use serde::{Deserialize, Serialize};
4use space::services::spaces::UserBriefDto;
4use space::services::spaces::UserDto;
55use time::OffsetDateTime;
66use utoipa::{IntoParams, ToSchema};
77use uuid::Uuid;
@@ -14,7 +14,7 @@
1414pub struct ContentHistoryDto {
1515 pub id: Uuid,
1616 pub issue_id: Uuid,
17 pub poster: Option<UserBriefDto>,
17 pub poster: Option<UserDto>,
1818 pub comment_id: Option<Uuid>,
1919 pub content_before: Option<String>,
2020 pub content_after: Option<String>,
Modified lib/issues/src/services/events.rs lib/issues/src/services/events.rs +2 −2 View lib/issues/src/services/events.rs
@@ -1,7 +1,7 @@
11use models::issue::issue_event_line::{self, IssueEventType};
22use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
33use serde::{Deserialize, Serialize};
4use space::services::spaces::UserBriefDto;
4use space::services::spaces::UserDto;
55use time::OffsetDateTime;
66use utoipa::{IntoParams, ToSchema};
77use uuid::Uuid;
@@ -14,7 +14,7 @@
1414pub struct EventDto {
1515 pub id: Uuid,
1616 pub issue_id: Option<Uuid>,
17 pub user: Option<UserBriefDto>,
17 pub user: Option<UserDto>,
1818 pub event_type: IssueEventType,
1919 pub comment_id: Option<Uuid>,
2020 pub commit_sha: Option<String>,
Modified lib/issues/src/services/issue_users.rs lib/issues/src/services/issue_users.rs +2 −2 View lib/issues/src/services/issue_users.rs
@@ -4,7 +4,7 @@
44 QuerySelect, Set,
55};
66use serde::{Deserialize, Serialize};
7use space::services::spaces::UserBriefDto;
7use space::services::spaces::UserDto;
88use time::OffsetDateTime;
99use utoipa::{IntoParams, ToSchema};
1010use uuid::Uuid;
@@ -17,7 +17,7 @@
1717pub struct IssueUserDto {
1818 pub id: Uuid,
1919 pub user_id: Uuid,
20 pub user: Option<UserBriefDto>,
20 pub user: Option<UserDto>,
2121 pub issue_id: Uuid,
2222 pub is_read: bool,
2323 pub created_at: OffsetDateTime,
Modified lib/issues/src/services/issues.rs lib/issues/src/services/issues.rs +6 −6 View lib/issues/src/services/issues.rs
@@ -25,7 +25,7 @@
2525 QueryFilter, QueryOrder, QuerySelect, Set, TransactionTrait,
2626};
2727use serde::{Deserialize, Serialize};
28use space::services::spaces::UserBriefDto;
28use space::services::spaces::UserDto;
2929use time::OffsetDateTime;
3030use utoipa::{IntoParams, ToSchema};
3131use uuid::Uuid;
@@ -47,14 +47,14 @@
4747 pub id: Uuid,
4848 pub board_id: Uuid,
4949 pub number: i32,
50 pub author: Option<UserBriefDto>,
50 pub author: Option<UserDto>,
5151 pub title: String,
5252 pub description: Option<String>,
5353 pub content: String,
5454 pub milestone_id: Option<Uuid>,
5555 pub priority: IssuePriority,
5656 pub due_at: Option<OffsetDateTime>,
57 pub assignees: Vec<UserBriefDto>,
57 pub assignees: Vec<UserDto>,
5858 pub viewer_watch: Option<models::common::watch_mode::WatchMode>,
5959 pub state: IssueState,
6060 pub created_at: OffsetDateTime,
@@ -67,12 +67,12 @@
6767 pub id: Uuid,
6868 pub board_id: Uuid,
6969 pub number: i32,
70 pub author: Option<UserBriefDto>,
70 pub author: Option<UserDto>,
7171 pub title: String,
7272 pub milestone_id: Option<Uuid>,
7373 pub priority: IssuePriority,
7474 pub due_at: Option<OffsetDateTime>,
75 pub assignees: Vec<UserBriefDto>,
75 pub assignees: Vec<UserDto>,
7676 pub state: IssueState,
7777 pub created_at: OffsetDateTime,
7878 pub updated_at: Option<OffsetDateTime>,
@@ -242,7 +242,7 @@
242242 pub(crate) async fn load_assignee_map(
243243 &self,
244244 issue_ids: &[Uuid],
245 ) -> Result<std::collections::HashMap<Uuid, Vec<UserBriefDto>>, IssueError> {
245 ) -> Result<std::collections::HashMap<Uuid, Vec<UserDto>>, IssueError> {
246246 let mut map = std::collections::HashMap::new();
247247 if issue_ids.is_empty() {
248248 return Ok(map);
Modified lib/issues/src/services/members.rs lib/issues/src/services/members.rs +2 −2 View lib/issues/src/services/members.rs
@@ -7,7 +7,7 @@
77 QuerySelect, Set, TransactionTrait,
88};
99use serde::{Deserialize, Serialize};
10use space::services::spaces::UserBriefDto;
10use space::services::spaces::UserDto;
1111use time::OffsetDateTime;
1212use utoipa::{IntoParams, ToSchema};
1313use uuid::Uuid;
@@ -21,7 +21,7 @@
2121pub struct IssueMemberDto {
2222 pub id: Uuid,
2323 pub issue_id: Uuid,
24 pub user: Option<UserBriefDto>,
24 pub user: Option<UserDto>,
2525 pub kind: IssueMemberKind,
2626 pub created_at: OffsetDateTime,
2727 pub updated_at: Option<OffsetDateTime>,
Modified lib/issues/src/services/reactions.rs lib/issues/src/services/reactions.rs +2 −2 View lib/issues/src/services/reactions.rs
@@ -6,7 +6,7 @@
66use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
77use serde::{Deserialize, Serialize};
88use space::services::join_rules::apply_space_join_rules;
9use space::services::spaces::UserBriefDto;
9use space::services::spaces::UserDto;
1010use time::OffsetDateTime;
1111use utoipa::{IntoParams, ToSchema};
1212use uuid::Uuid;
@@ -27,7 +27,7 @@
2727 pub id: Uuid,
2828 pub scope: ReactionScope,
2929 pub target_id: Uuid,
30 pub user: Option<UserBriefDto>,
30 pub user: Option<UserDto>,
3131 pub emoji: String,
3232 pub created_at: OffsetDateTime,
3333}
Modified lib/issues/src/services/service.rs lib/issues/src/services/service.rs +3 −3 View lib/issues/src/services/service.rs
@@ -16,7 +16,7 @@
1616use sea_orm::{ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, Set};
1717use session::SessionError;
1818use session::share::guard::{SessionGuard, SessionWithUser};
19use space::services::spaces::UserBriefDto;
19use space::services::spaces::UserDto;
2020use time::OffsetDateTime;
2121use uuid::Uuid;
2222
@@ -47,7 +47,7 @@
4747 pub async fn load_user_briefs(
4848 &self,
4949 uids: &[Uuid],
50 ) -> Result<HashMap<Uuid, UserBriefDto>, IssueError> {
50 ) -> Result<HashMap<Uuid, UserDto>, IssueError> {
5151 if uids.is_empty() {
5252 return Ok(HashMap::new());
5353 }
@@ -60,7 +60,7 @@
6060 .await?;
6161 let map = users
6262 .into_iter()
63 .map(|u| (u.uid, UserBriefDto::from(u)))
63 .map(|u| (u.uid, UserDto::from(u)))
6464 .collect();
6565 Ok(map)
6666 }
Modified lib/issues/src/services/stopwatch.rs lib/issues/src/services/stopwatch.rs +2 −2 View lib/issues/src/services/stopwatch.rs
@@ -2,7 +2,7 @@
22use models::issue::tracked_time;
33use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set, TransactionTrait};
44use serde::{Deserialize, Serialize};
5use space::services::spaces::UserBriefDto;
5use space::services::spaces::UserDto;
66use time::OffsetDateTime;
77use utoipa::{IntoParams, ToSchema};
88use uuid::Uuid;
@@ -18,7 +18,7 @@
1818 pub id: Option<Uuid>,
1919 pub issue_id: Uuid,
2020 #[serde(skip_serializing_if = "Option::is_none")]
21 pub user: Option<UserBriefDto>,
21 pub user: Option<UserDto>,
2222 #[serde(skip_serializing_if = "Option::is_none")]
2323 pub started_at: Option<OffsetDateTime>,
2424 #[serde(skip_serializing_if = "Option::is_none")]
Modified lib/issues/src/services/tracked_time.rs lib/issues/src/services/tracked_time.rs +2 −2 View lib/issues/src/services/tracked_time.rs
@@ -1,7 +1,7 @@
11use models::issue::tracked_time;
22use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, Set};
33use serde::{Deserialize, Serialize};
4use space::services::spaces::UserBriefDto;
4use space::services::spaces::UserDto;
55use time::OffsetDateTime;
66use utoipa::{IntoParams, ToSchema};
77use uuid::Uuid;
@@ -14,7 +14,7 @@
1414pub struct TrackedTimeDto {
1515 pub id: Uuid,
1616 pub issue_id: Uuid,
17 pub user: Option<UserBriefDto>,
17 pub user: Option<UserDto>,
1818 pub seconds: i64,
1919 pub created_at: OffsetDateTime,
2020}
Modified lib/issues/src/services/watches.rs lib/issues/src/services/watches.rs +2 −2 View lib/issues/src/services/watches.rs
@@ -2,7 +2,7 @@
22use models::issue::issue_watch;
33use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
44use serde::{Deserialize, Serialize};
5use space::services::spaces::UserBriefDto;
5use space::services::spaces::UserDto;
66use time::OffsetDateTime;
77use utoipa::{IntoParams, ToSchema};
88use uuid::Uuid;
@@ -15,7 +15,7 @@
1515pub struct WatchDto {
1616 pub id: Uuid,
1717 pub issue_id: Option<Uuid>,
18 pub user: Option<UserBriefDto>,
18 pub user: Option<UserDto>,
1919 pub mode: WatchMode,
2020 pub created_at: OffsetDateTime,
2121 pub updated_at: Option<OffsetDateTime>,
Modified lib/issues/src/openapi.rs lib/issues/src/openapi.rs +1 −1 View lib/issues/src/openapi.rs
@@ -101,7 +101,7 @@
101101 AppResponseData<crate::services::repo_relations::RepoRelDto>,
102102 AppResponseData<Vec<crate::services::repo_relations::RepoRelDto>>,
103103 export::rest::NoData,
104 space::services::spaces::UserBriefDto,
104 space::services::spaces::UserDto,
105105 space::services::spaces::SpaceNamePath,
106106 crate::services::boards::BoardDto,
107107 crate::services::boards::BoardPage,
Modified lib/space/src/services/bans.rs lib/space/src/services/bans.rs +3 −3 View lib/space/src/services/bans.rs
@@ -14,16 +14,16 @@
1414
1515use crate::errors::SpaceError;
1616use crate::services::service::SpaceService;
17use crate::services::spaces::{PageQuery, UserBriefDto};
17use crate::services::spaces::{PageQuery, UserDto};
1818use crate::share::authz::{MANAGE_BANS, require_perm};
1919use crate::share::lookup::lookup_space_by_name;
2020
2121#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
2222pub struct BanDto {
2323 pub space_id: Uuid,
24 pub user: Option<UserBriefDto>,
24 pub user: Option<UserDto>,
2525 pub reason: Option<String>,
26 pub banned_by: Option<UserBriefDto>,
26 pub banned_by: Option<UserDto>,
2727 pub created_at: OffsetDateTime,
2828 pub updated_at: OffsetDateTime,
2929 pub expires_at: Option<OffsetDateTime>,
Modified lib/space/src/services/dir.rs lib/space/src/services/dir.rs +4 −4 View lib/space/src/services/dir.rs
@@ -17,7 +17,7 @@
1717
1818use crate::errors::SpaceError;
1919use crate::services::service::SpaceService;
20use crate::services::spaces::UserBriefDto;
20use crate::services::spaces::UserDto;
2121use crate::share::authz::{MANAGE_CHANNELS, MANAGE_OVERWRITES, effective_permissions, require_perm};
2222use crate::share::lookup::lookup_space_by_name;
2323
@@ -39,7 +39,7 @@
3939 pub archived_at: Option<OffsetDateTime>,
4040 pub created_at: OffsetDateTime,
4141 pub updated_at: OffsetDateTime,
42 pub created_by_uid: Uuid,
42 pub created_by_id: Uuid,
4343 #[schema(no_recursion)]
4444 pub children: Vec<DirNodeDto>,
4545}
@@ -60,7 +60,7 @@
6060 archived_at: m.archived_at,
6161 created_at: m.created_at,
6262 updated_at: m.updated_at,
63 created_by_uid: m.created_by_uid,
63 created_by_id: m.created_by_uid,
6464 children: Vec::new(),
6565 }
6666 }
@@ -106,7 +106,7 @@
106106pub struct UserOverwriteDto {
107107 pub dir_id: Uuid,
108108 pub user_id: Uuid,
109 pub user: Option<UserBriefDto>,
109 pub user: Option<UserDto>,
110110 pub allow_mask: i64,
111111 pub deny_mask: i64,
112112}
Modified lib/space/src/services/groups.rs lib/space/src/services/groups.rs +2 −2 View lib/space/src/services/groups.rs
@@ -15,7 +15,7 @@
1515
1616use crate::errors::SpaceError;
1717use crate::services::service::SpaceService;
18use crate::services::spaces::{PageQuery, UserBriefDto};
18use crate::services::spaces::{PageQuery, UserDto};
1919use crate::share::authz::{
2020 ALL_PERMISSIONS, MANAGE_ROLES, VIEW_SPACE, effective_permissions, require_perm,
2121};
@@ -33,7 +33,7 @@
3333 pub version: i32,
3434 pub created_at: OffsetDateTime,
3535 pub updated_at: Option<OffsetDateTime>,
36 pub created_by: Option<UserBriefDto>,
36 pub created_by: Option<UserDto>,
3737 pub permissions: Vec<String>,
3838}
3939
Modified lib/space/src/services/images.rs lib/space/src/services/images.rs +13 −1 View lib/space/src/services/images.rs
@@ -117,6 +117,7 @@
117117 .await?;
118118
119119 let url = format!("storage://{key}");
120 let space_for_url_lower = space.lower_name.clone();
120121 let now = OffsetDateTime::now_utc();
121122
122123 let mut am: spaces::ActiveModel = space.into();
@@ -169,7 +170,12 @@
169170 Ok(SpaceImageDto {
170171 space_id,
171172 kind,
172 url,
173 url: crate::services::spaces::resolve_space_image_url(
174 &space_for_url_lower,
175 kind.as_str(),
176 Some(url),
177 )
178 .expect("resolved"),
173179 format: fmt,
174180 width: cmd.width,
175181 height: cmd.height,
@@ -285,6 +291,12 @@
285291 let ext = url.split('.').next_back().unwrap_or("jpg");
286292 let format = AvatarFormat::from_extension(ext).unwrap_or(AvatarFormat::Jpg);
287293
294 let url = crate::services::spaces::resolve_space_image_url(
295 &space.lower_name.clone(),
296 kind.as_str(),
297 Some(url),
298 )
299 .expect("resolved");
288300 Ok(Some(SpaceImageDto {
289301 space_id: space.id,
290302 kind,
Modified lib/space/src/services/invites.rs lib/space/src/services/invites.rs +2 −2 View lib/space/src/services/invites.rs
@@ -21,7 +21,7 @@
2121
2222use crate::errors::SpaceError;
2323use crate::services::service::SpaceService;
24use crate::services::spaces::{PageQuery, UserBriefDto};
24use crate::services::spaces::{PageQuery, UserDto};
2525use crate::share::authz::{MANAGE_INVITES, assert_not_banned, effective_permissions, require_perm};
2626use crate::share::lookup::lookup_space_by_name;
2727
@@ -29,7 +29,7 @@
2929pub struct InviteDto {
3030 pub id: Uuid,
3131 pub space_id: Uuid,
32 pub user: Option<UserBriefDto>,
32 pub user: Option<UserDto>,
3333 pub email: String,
3434 pub groups: Vec<Uuid>,
3535 pub created_at: OffsetDateTime,
Modified lib/space/src/services/members.rs lib/space/src/services/members.rs +3 −3 View lib/space/src/services/members.rs
@@ -14,21 +14,21 @@
1414
1515use crate::errors::SpaceError;
1616use crate::services::service::SpaceService;
17use crate::services::spaces::{PageQuery, UserBriefDto};
17use crate::services::spaces::{PageQuery, UserDto};
1818use crate::share::authz::{MANAGE_MEMBERS, assert_not_banned, require_perm};
1919use crate::share::lookup::lookup_space_by_name;
2020
2121#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
2222pub struct MemberDto {
2323 pub space_id: Uuid,
24 pub user: Option<UserBriefDto>,
24 pub user: Option<UserDto>,
2525 pub alias_name: Option<String>,
2626 pub alias_avatar_url: Option<String>,
2727 pub status: MemberStatus,
2828 pub last_active_at: Option<OffsetDateTime>,
2929 pub left_at: Option<OffsetDateTime>,
3030 pub invited_at: Option<OffsetDateTime>,
31 pub invited_by: Option<UserBriefDto>,
31 pub invited_by: Option<UserDto>,
3232 pub joined_at: OffsetDateTime,
3333 pub created_at: OffsetDateTime,
3434 pub updated_at: Option<OffsetDateTime>,
Modified lib/space/src/services/spaces.rs lib/space/src/services/spaces.rs +44 −12 View lib/space/src/services/spaces.rs
@@ -29,21 +29,41 @@
2929}
3030
3131#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
32pub struct UserBriefDto {
32pub struct UserDto {
3333 pub user_id: Uuid,
3434 pub username: String,
3535 pub display_name: String,
3636 pub avatar_url: String,
3737}
3838
39impl From<user::Model> for UserBriefDto {
39impl From<user::Model> for UserDto {
4040 fn from(u: user::Model) -> Self {
4141 Self {
4242 user_id: u.uid,
4343 username: u.username,
4444 display_name: u.display_name,
45 avatar_url: u.avatar_url,
45 avatar_url: resolve_user_avatar_url(u.uid, &u.avatar_url),
46 }
47 }
48}
49
50pub fn resolve_space_image_url(space_lower: &str, kind: &str, raw: Option<String>) -> Option<String> {
51 raw.map(|u| {
52 if u.starts_with("storage://") {
53 format!("/api/v1/spaces/{space_lower}/images/{kind}")
54 } else {
55 u
4656 }
57 })
58}
59
60pub fn resolve_user_avatar_url(uid: uuid::Uuid, raw: &str) -> String {
61 if raw.is_empty() {
62 String::new()
63 } else if raw.starts_with("storage://") {
64 format!("/api/v1/users/{uid}/avatar")
65 } else {
66 raw.to_string()
4767 }
4868}
4969
@@ -53,7 +73,7 @@
5373 pub lower_name: String,
5474 pub name: String,
5575 pub description: Option<String>,
56 pub owner: Option<UserBriefDto>,
76 pub owner: Option<UserDto>,
5777 pub avatar_url: Option<String>,
5878 pub is_private: bool,
5979 pub is_active: bool,
@@ -126,7 +146,7 @@
126146 pub async fn load_user_briefs(
127147 &self,
128148 uids: &[Uuid],
129 ) -> Result<HashMap<Uuid, UserBriefDto>, SpaceError> {
149 ) -> Result<HashMap<Uuid, UserDto>, SpaceError> {
130150 if uids.is_empty() {
131151 return Ok(HashMap::new());
132152 }
@@ -142,7 +162,7 @@
142162 .await?;
143163 let map = users
144164 .into_iter()
145 .map(|u| (u.uid, UserBriefDto::from(u)))
165 .map(|u| (u.uid, UserDto::from(u)))
146166 .collect();
147167 Ok(map)
148168 }
@@ -280,11 +300,15 @@
280300
281301 Ok(SpaceDto {
282302 id: inserted_space.id,
283 lower_name: inserted_space.lower_name,
303 lower_name: inserted_space.lower_name.clone(),
284304 name: inserted_space.name,
285305 description: inserted_space.description,
286306 owner,
287 avatar_url: inserted_space.avatar_url,
307 avatar_url: resolve_space_image_url(
308 &inserted_space.lower_name,
309 "avatar",
310 inserted_space.avatar_url,
311 ),
288312 is_private: inserted_space.is_private,
289313 is_active: inserted_space.is_active,
290314 allow_join: inserted_space.allow_join,
@@ -325,11 +349,15 @@
325349
326350 Ok(SpaceDto {
327351 id: space.id,
328 lower_name: space.lower_name,
352 lower_name: space.lower_name.clone(),
329353 name: space.name,
330354 description: space.description,
331355 owner,
332 avatar_url: space.avatar_url,
356 avatar_url: resolve_space_image_url(
357 &space.lower_name,
358 "avatar",
359 space.avatar_url,
360 ),
333361 is_private: space.is_private,
334362 is_active: space.is_active,
335363 allow_join: space.allow_join,
@@ -435,11 +463,15 @@
435463
436464 Ok(SpaceDto {
437465 id: updated.id,
438 lower_name: updated.lower_name,
466 lower_name: updated.lower_name.clone(),
439467 name: updated.name,
440468 description: updated.description,
441469 owner,
442 avatar_url: updated.avatar_url,
470 avatar_url: resolve_space_image_url(
471 &updated.lower_name,
472 "avatar",
473 updated.avatar_url,
474 ),
443475 is_private: updated.is_private,
444476 is_active: updated.is_active,
445477 allow_join: updated.allow_join,
Modified lib/space/src/openapi.rs lib/space/src/openapi.rs +1 −1 View lib/space/src/openapi.rs
@@ -80,7 +80,7 @@
8080 export::pagination::Page<crate::services::spaces::SpaceDto>,
8181 crate::services::spaces::CreateSpaceCmd,
8282 crate::services::spaces::UpdateSpaceCmd,
83 crate::services::spaces::UserBriefDto,
83 crate::services::spaces::UserDto,
8484 crate::services::groups::GroupDto,
8585 crate::services::groups::GroupPage,
8686 crate::services::groups::CreateGroupCmd,
Modified lib/user/src/services/notifications.rs lib/user/src/services/notifications.rs +2 −2 View lib/user/src/services/notifications.rs
@@ -35,7 +35,7 @@
3535#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
3636pub struct ReportDto {
3737 pub id: Uuid,
38 pub reported_uid: Uuid,
38 pub reported_id: Uuid,
3939 pub reason: String,
4040 pub evidence: Option<String>,
4141 pub status: ReportStatus,
@@ -48,7 +48,7 @@
4848 fn from(m: user_reports::Model) -> Self {
4949 Self {
5050 id: m.id,
51 reported_uid: m.reported_uid,
51 reported_id: m.reported_uid,
5252 reason: m.reason,
5353 evidence: m.evidence,
5454 status: m.status,
Modified lib/user/src/services/profile.rs lib/user/src/services/profile.rs +8 −2 View lib/user/src/services/profile.rs
@@ -355,7 +355,10 @@
355355 user_id: user.uid,
356356 username: user.username,
357357 display_name: user.display_name,
358 avatar_url: user.avatar_url,
358 avatar_url: space::services::spaces::resolve_user_avatar_url(
359 user.uid,
360 &user.avatar_url,
361 ),
359362 visibility: if is_owner { Some(user.visibility) } else { None },
360363 real_name: gate(p.show_real_name, p.real_name),
361364 bio: gate(emit_activity, p.bio),
@@ -376,7 +379,10 @@
376379 user_id: user.uid,
377380 username: user.username,
378381 display_name: user.display_name,
379 avatar_url: user.avatar_url,
382 avatar_url: space::services::spaces::resolve_user_avatar_url(
383 user.uid,
384 &user.avatar_url,
385 ),
380386 visibility: if is_owner { Some(user.visibility) } else { None },
381387 real_name: None,
382388 bio: None,
Modified lib/user/Cargo.toml lib/user/Cargo.toml +2 −1 View lib/user/Cargo.toml
@@ -22,4 +22,5 @@
2222base64 = { workspace = true }
2323rand = { workspace = true }
2424sha2 = { workspace = true }
25tracing = { workspace = true }
No newline at end of file
25tracing = { workspace = true }
26space = { workspace = true }
No newline at end of file
Modified Cargo.lock Cargo.lock +3 −0 View Cargo.lock
@@ -598,6 +598,7 @@
598598 "session",
599599 "sha1 0.11.0",
600600 "sha2 0.11.0",
601 "space",
601602 "ssh-key 0.6.7",
602603 "time 0.3.55",
603604 "tokio",
@@ -2624,6 +2625,7 @@
26242625 "serde_json",
26252626 "session",
26262627 "sha2 0.11.0",
2628 "space",
26272629 "ssh-key 0.6.7",
26282630 "thiserror 2.0.20",
26292631 "time 0.3.55",
@@ -8607,6 +8609,7 @@
86078609 "serde_json",
86088610 "session",
86098611 "sha2 0.11.0",
8612 "space",
86108613 "storage",
86118614 "time 0.3.55",
86128615 "tracing",