refactor: simplify pagination logic across multiple modules for improved consistency and clarity
ZhenYi committed
Modified lib/chat/src/services/messages.rs lib/chat/src/services/messages.rs +6 −4 View lib/chat/src/services/messages.rs
@@ -865,10 +865,12 @@
865865 ) -> Result<MessagePage, ChatError> {
866866 let _ctx = require_room_readable(self, room_id, caller).await?;
867867
868 let limit = q
869 .limit
870 .unwrap_or(self.config().chat.default_pagination)
871 .clamp(1, self.config().chat.max_pagination);
868 let (limit, _) = export::pagination::page_bounds(
869 q.limit,
870 None,
871 self.config().chat.default_pagination,
872 self.config().chat.max_pagination,
873 );
872874
873875 let mut select = messages::Entity::find()
874876 .filter(messages::Column::RoomId.eq(room_id))
Modified lib/chat/src/services/rooms.rs lib/chat/src/services/rooms.rs +7 −9 View lib/chat/src/services/rooms.rs
@@ -710,20 +710,18 @@
710710 q: RoomMemberQuery,
711711 ) -> Result<Vec<RoomMemberDto>, ChatError> {
712712 let _ctx = require_room_readable(self, room_id, caller).await?;
713 let limit = q
714 .limit
715 .unwrap_or(self.config().chat.default_pagination)
716 .clamp(1, self.config().chat.max_pagination);
713 let (limit, offset) = export::pagination::page_bounds(
714 q.limit,
715 q.offset,
716 self.config().chat.default_pagination,
717 self.config().chat.max_pagination,
718 );
717719 let rows = room_members::Entity::find()
718720 .filter(room_members::Column::RoomId.eq(room_id))
719721 .filter(room_members::Column::LeftAt.is_null())
720722 .order_by_asc(room_members::Column::JoinedAt)
721723 .limit(limit)
722 .offset(
723 q.offset
724 .unwrap_or(0)
725 .clamp(0, export::pagination::MAX_OFFSET),
726 )
724 .offset(offset)
727725 .all(self.db().read())
728726 .await?;
729727 let uids: Vec<i64> = rows.iter().map(|r| r.user_id).collect();
Modified lib/chat/src/services/service.rs lib/chat/src/services/service.rs +1 −11 View lib/chat/src/services/service.rs
@@ -35,17 +35,7 @@
3535 }
3636
3737 pub async fn load_user_briefs(&self, uids: &[i64]) -> Result<HashMap<i64, UserDto>, ChatError> {
38 if uids.is_empty() {
39 return Ok(HashMap::new());
40 }
41 let mut set: Vec<i64> = uids.to_vec();
42 set.sort();
43 set.dedup();
44 let users = export::users::load_public_users(&self.state, &set).await?;
45 Ok(users
46 .into_iter()
47 .map(|(uid, public)| (uid, UserDto::from_public(&public)))
48 .collect())
38 Ok(export::users::load_user_briefs(&self.state, uids).await?)
4939 }
5040
5141 /// Language for one recipient's notification/e-mail text.
Modified lib/chat/src/services/threads.rs lib/chat/src/services/threads.rs +6 −8 View lib/chat/src/services/threads.rs
@@ -248,14 +248,12 @@
248248 q: ListThreadQuery,
249249 ) -> Result<ThreadPage, ChatError> {
250250 let _ctx = lookup::require_room_readable(self, room_id, caller).await?;
251 let limit = q
252 .limit
253 .unwrap_or(self.config().chat.default_pagination)
254 .clamp(1, self.config().chat.max_pagination);
255 let offset = q
256 .offset
257 .unwrap_or(0)
258 .clamp(0, export::pagination::MAX_OFFSET);
251 let (limit, offset) = export::pagination::page_bounds(
252 q.limit,
253 q.offset,
254 self.config().chat.default_pagination,
255 self.config().chat.max_pagination,
256 );
259257
260258 let mut select = threads::Entity::find().filter(threads::Column::RoomId.eq(room_id));
261259 if let Some(archived) = q.archived {
Modified lib/chat/src/share/lookup.rs lib/chat/src/share/lookup.rs +1 −9 View lib/chat/src/share/lookup.rs
@@ -74,15 +74,7 @@
7474 service: &ChatService,
7575 space_name: &str,
7676) -> Result<spaces::Model, ChatError> {
77 let lower_name = space_name.trim().to_lowercase();
78 if lower_name.is_empty() {
79 return Err(ChatError::NotFound);
80 }
81 let space = space::authority::space_by_slug(&service.state, 0, &lower_name).await?;
82 if !space.is_active {
83 return Err(ChatError::NotFound);
84 }
85 Ok(space)
77 Ok(space::share::lookup::find_active_space_by_name(&service.state, 0, space_name).await?)
8678}
8779
8880pub async fn require_space_member(
Modified lib/export/src/pagination.rs lib/export/src/pagination.rs +7 −0 View lib/export/src/pagination.rs
@@ -12,6 +12,13 @@
1212 pub sort: Option<String>,
1313}
1414
15pub fn page_bounds(limit: Option<u64>, offset: Option<u64>, default: u64, max: u64) -> (u64, u64) {
16 (
17 limit.unwrap_or(default).clamp(1, max),
18 offset.unwrap_or(0).clamp(0, MAX_OFFSET),
19 )
20}
21
1522impl PageQuery {
1623 pub fn limit(&self) -> u64 {
1724 self.limit
Modified lib/export/src/users.rs lib/export/src/users.rs +34 −0 View lib/export/src/users.rs
@@ -5,6 +5,40 @@
55use crate::AppState;
66use crate::error::ExportError;
77
8#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
9pub struct UserDto {
10 pub user_id: i64,
11 pub username: String,
12 pub display_name: String,
13 pub avatar_url: String,
14}
15
16impl UserDto {
17 pub fn from_public(public: &UserPublic) -> Self {
18 Self {
19 user_id: public.user_id,
20 username: public.username.clone(),
21 display_name: if public.displayname.trim().is_empty() {
22 public.username.clone()
23 } else {
24 public.displayname.clone()
25 },
26 avatar_url: crate::urls::resolve_user_avatar_url(public.user_id, &public.avatar_url),
27 }
28 }
29}
30
31pub async fn load_user_briefs(
32 state: &AppState,
33 uids: &[i64],
34) -> Result<HashMap<i64, UserDto>, ExportError> {
35 let users = load_public_users(state, uids).await?;
36 Ok(users
37 .into_iter()
38 .map(|(uid, public)| (uid, UserDto::from_public(&public)))
39 .collect())
40}
41
842/// Bulk identity lookup. KenApi keeps no local copy of usernames, display
943/// names or avatars: every caller goes through KenOAuth.
1044pub async fn load_public_users(
Modified lib/issues/src/services/labels.rs lib/issues/src/services/labels.rs +1 −7 View lib/issues/src/services/labels.rs
@@ -190,13 +190,7 @@
190190 ) -> Result<LabelPage, IssueError> {
191191 let (_, board) = board_context(self, caller, space_name, board_id).await?;
192192
193 let max_pagination = self.config().issues.max_pagination;
194 let default_pagination = self.config().issues.default_pagination;
195 let limit = q
196 .limit
197 .unwrap_or(default_pagination)
198 .clamp(1, max_pagination);
199 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
193 let (limit, offset) = self.paginate(q.limit, q.offset);
200194
201195 let mut select =
202196 issue_label::Entity::find().filter(issue_label::Column::DirId.eq(board.dir_id));
Modified lib/issues/src/services/service.rs lib/issues/src/services/service.rs +6 −16 View lib/issues/src/services/service.rs
@@ -42,11 +42,11 @@
4242 }
4343
4444 pub fn paginate(&self, limit: Option<u64>, offset: Option<u64>) -> (u64, u64) {
45 let max_pagination = self.config().issues.max_pagination;
46 let default_pagination = self.config().issues.default_pagination;
47 (
48 limit.unwrap_or(default_pagination).clamp(1, max_pagination),
49 offset.unwrap_or(0).clamp(0, 5000),
45 export::pagination::page_bounds(
46 limit,
47 offset,
48 self.config().issues.default_pagination,
49 self.config().issues.max_pagination,
5050 )
5151 }
5252
@@ -58,17 +58,7 @@
5858 &self,
5959 uids: &[i64],
6060 ) -> Result<HashMap<i64, UserDto>, IssueError> {
61 if uids.is_empty() {
62 return Ok(HashMap::new());
63 }
64 let mut set: Vec<i64> = uids.to_vec();
65 set.sort();
66 set.dedup();
67 let users = export::users::load_public_users(&self.state, &set).await?;
68 Ok(users
69 .into_iter()
70 .map(|(uid, public)| (uid, UserDto::from_public(&public)))
71 .collect())
61 Ok(export::users::load_user_briefs(&self.state, uids).await?)
7262 }
7363
7464 #[allow(clippy::too_many_arguments)]
Modified lib/issues/src/share/lookup.rs lib/issues/src/share/lookup.rs +1 −9 View lib/issues/src/share/lookup.rs
@@ -12,15 +12,7 @@
1212 service: &IssueService,
1313 space_name: &str,
1414) -> Result<spaces::Model, IssueError> {
15 let lower = space_name.trim().to_lowercase();
16 if lower.is_empty() {
17 return Err(IssueError::NotFound);
18 }
19 let space = space::authority::space_by_slug(&service.state, 0, &lower).await?;
20 if !space.is_active {
21 return Err(IssueError::NotFound);
22 }
23 Ok(space)
15 Ok(space::share::lookup::find_active_space_by_name(&service.state, 0, space_name).await?)
2416}
2517
2618pub async fn lookup_board(
Modified lib/space/src/services/bans.rs lib/space/src/services/bans.rs +6 −10 View lib/space/src/services/bans.rs
@@ -330,16 +330,12 @@
330330 let space = lookup::lookup_space_by_name(self, caller, space_name).await?;
331331 require_perm(self, &space, caller, MANAGE_BANS).await?;
332332
333 let max_pagination = self.config().space.max_pagination;
334 let default_pagination = self.config().space.default_pagination;
335 let limit = q
336 .limit
337 .unwrap_or(default_pagination)
338 .clamp(1, max_pagination);
339 let offset = q
340 .offset
341 .unwrap_or(0)
342 .clamp(0, export::pagination::MAX_OFFSET);
333 let (limit, offset) = export::pagination::page_bounds(
334 q.limit,
335 q.offset,
336 self.config().space.default_pagination,
337 self.config().space.max_pagination,
338 );
343339
344340 let select = space_bans::Entity::find()
345341 .filter(space_bans::Column::SpaceId.eq(space.id))
Modified lib/space/src/services/groups.rs lib/space/src/services/groups.rs +6 −10 View lib/space/src/services/groups.rs
@@ -490,16 +490,12 @@
490490 let space = lookup::lookup_space_by_name(self, caller, space_name).await?;
491491 require_perm(self, &space, caller, MANAGE_ROLES).await?;
492492
493 let max_pagination = self.config().space.max_pagination;
494 let default_pagination = self.config().space.default_pagination;
495 let limit = q
496 .limit
497 .unwrap_or(default_pagination)
498 .clamp(1, max_pagination);
499 let offset = q
500 .offset
501 .unwrap_or(0)
502 .clamp(0, export::pagination::MAX_OFFSET);
493 let (limit, offset) = export::pagination::page_bounds(
494 q.limit,
495 q.offset,
496 self.config().space.default_pagination,
497 self.config().space.max_pagination,
498 );
503499
504500 let select = space_groups::Entity::find()
505501 .filter(space_groups::Column::SpaceId.eq(space.id))
Modified lib/space/src/services/invites.rs lib/space/src/services/invites.rs +6 −10 View lib/space/src/services/invites.rs
@@ -568,16 +568,12 @@
568568 let space = lookup::lookup_space_by_name(self, caller, space_name).await?;
569569 require_perm(self, &space, caller, MANAGE_INVITES).await?;
570570
571 let max_pagination = self.config().space.max_pagination;
572 let default_pagination = self.config().space.default_pagination;
573 let limit = q
574 .limit
575 .unwrap_or(default_pagination)
576 .clamp(1, max_pagination);
577 let offset = q
578 .offset
579 .unwrap_or(0)
580 .clamp(0, export::pagination::MAX_OFFSET);
571 let (limit, offset) = export::pagination::page_bounds(
572 q.limit,
573 q.offset,
574 self.config().space.default_pagination,
575 self.config().space.max_pagination,
576 );
581577
582578 let select = space_invite::Entity::find()
583579 .filter(space_invite::Column::SpaceId.eq(space.id))
Modified lib/space/src/services/join_requests.rs lib/space/src/services/join_requests.rs +12 −20 View lib/space/src/services/join_requests.rs
@@ -234,16 +234,12 @@
234234 viewer: Option<i64>,
235235 query: ExploreQuery,
236236 ) -> Result<ExploreSpacePage, SpaceError> {
237 let max_pagination = self.config().space.max_pagination;
238 let default_pagination = self.config().space.default_pagination;
239 let limit = query
240 .limit
241 .unwrap_or(default_pagination)
242 .clamp(1, max_pagination);
243 let offset = query
244 .offset
245 .unwrap_or(0)
246 .clamp(0, export::pagination::MAX_OFFSET);
237 let (limit, offset) = export::pagination::page_bounds(
238 query.limit,
239 query.offset,
240 self.config().space.default_pagination,
241 self.config().space.max_pagination,
242 );
247243
248244 let mut rows = crate::authority::list_public_spaces(
249245 &self.state,
@@ -550,16 +546,12 @@
550546 let space = lookup::lookup_space_by_name(self, caller, space_name).await?;
551547 require_perm(self, &space, caller, MANAGE_MEMBERS).await?;
552548
553 let max_pagination = self.config().space.max_pagination;
554 let default_pagination = self.config().space.default_pagination;
555 let limit = query
556 .limit
557 .unwrap_or(default_pagination)
558 .clamp(1, max_pagination);
559 let offset = query
560 .offset
561 .unwrap_or(0)
562 .clamp(0, export::pagination::MAX_OFFSET);
549 let (limit, offset) = export::pagination::page_bounds(
550 query.limit,
551 query.offset,
552 self.config().space.default_pagination,
553 self.config().space.max_pagination,
554 );
563555
564556 let mut select = space_join_request::Entity::find()
565557 .filter(space_join_request::Column::SpaceId.eq(space.id));
Modified lib/space/src/services/members.rs lib/space/src/services/members.rs +6 −10 View lib/space/src/services/members.rs
@@ -381,16 +381,12 @@
381381 let space = lookup::lookup_space_by_name(self, caller, space_name).await?;
382382 require_perm(self, &space, caller, MANAGE_MEMBERS).await?;
383383
384 let max_pagination = self.config().space.max_pagination;
385 let default_pagination = self.config().space.default_pagination;
386 let limit = q
387 .limit
388 .unwrap_or(default_pagination)
389 .clamp(1, max_pagination);
390 let offset = q
391 .offset
392 .unwrap_or(0)
393 .clamp(0, export::pagination::MAX_OFFSET);
384 let (limit, offset) = export::pagination::page_bounds(
385 q.limit,
386 q.offset,
387 self.config().space.default_pagination,
388 self.config().space.max_pagination,
389 );
394390
395391 let mut rows = authority::list_members(&self.state, caller, space.id, true, true).await?;
396392 rows.sort_by_key(|member| member.joined_at);
Modified lib/space/src/services/spaces.rs lib/space/src/services/spaces.rs +8 −47 View lib/space/src/services/spaces.rs
@@ -29,29 +29,7 @@
2929 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
3030}
3131
32#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
33pub struct UserDto {
34 pub user_id: i64,
35 pub username: String,
36 pub display_name: String,
37 pub avatar_url: String,
38}
39
40impl UserDto {
41 pub fn from_public(public: &proto::auth::common::v1::UserPublic) -> Self {
42 Self {
43 user_id: public.user_id,
44 username: public.username.clone(),
45 display_name: if public.displayname.trim().is_empty() {
46 public.username.clone()
47 } else {
48 public.displayname.clone()
49 },
50 avatar_url: resolve_user_avatar_url(public.user_id, &public.avatar_url),
51 }
52 }
53}
54
32pub use export::users::UserDto;
5533pub use export::{resolve_space_image_url, resolve_user_avatar_url};
5634
5735#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
@@ -157,20 +135,7 @@
157135 &self,
158136 uids: &[i64],
159137 ) -> Result<HashMap<i64, UserDto>, SpaceError> {
160 if uids.is_empty() {
161 return Ok(HashMap::new());
162 }
163 let unique_uids: Vec<i64> = {
164 let mut set: Vec<i64> = uids.to_vec();
165 set.sort();
166 set.dedup();
167 set
168 };
169 let users = export::users::load_public_users(&self.state, &unique_uids).await?;
170 Ok(users
171 .into_iter()
172 .map(|(uid, public)| (uid, UserDto::from_public(&public)))
173 .collect())
138 Ok(export::users::load_user_briefs(&self.state, uids).await?)
174139 }
175140
176141 #[tracing::instrument(name = "space.create_space", skip_all, fields(caller = %caller))]
@@ -436,16 +401,12 @@
436401 }
437402
438403 pub async fn list_spaces(&self, caller: i64, q: PageQuery) -> Result<SpacePage, SpaceError> {
439 let max_pagination = self.config().space.max_pagination;
440 let default_pagination = self.config().space.default_pagination;
441 let limit = q
442 .limit
443 .unwrap_or(default_pagination)
444 .clamp(1, max_pagination);
445 let offset = q
446 .offset
447 .unwrap_or(0)
448 .clamp(0, export::pagination::MAX_OFFSET);
404 let (limit, offset) = export::pagination::page_bounds(
405 q.limit,
406 q.offset,
407 self.config().space.default_pagination,
408 self.config().space.max_pagination,
409 );
449410
450411 let all: Vec<spaces::Model> = authority::list_user_spaces(&self.state, caller, caller)
451412 .await?
Modified lib/space/src/share/lookup.rs lib/space/src/share/lookup.rs +11 −3 View lib/space/src/share/lookup.rs
@@ -5,8 +5,8 @@
55use crate::errors::SpaceError;
66use crate::services::service::SpaceService;
77
8pub async fn lookup_space_by_name(
9 service: &SpaceService,
8pub async fn find_active_space_by_name(
9 state: &export::AppState,
1010 caller: i64,
1111 space_name: &str,
1212) -> Result<spaces::Model, SpaceError> {
@@ -14,13 +14,21 @@
1414 if lower_name.is_empty() {
1515 return Err(SpaceError::NotFound);
1616 }
17 let space = authority::space_by_slug(&service.state, caller, &lower_name).await?;
17 let space = authority::space_by_slug(state, caller, &lower_name).await?;
1818 if !space.is_active {
1919 return Err(SpaceError::NotFound);
2020 }
2121 Ok(space)
2222}
2323
24pub async fn lookup_space_by_name(
25 service: &SpaceService,
26 caller: i64,
27 space_name: &str,
28) -> Result<spaces::Model, SpaceError> {
29 find_active_space_by_name(&service.state, caller, space_name).await
30}
31
2432pub async fn lookup_space_by_id(
2533 service: &SpaceService,
2634 caller: i64,