feat(pagination): update pagination handling across multiple services and queries
ZhenYi committed
This change is very large, so some diffs are not shown. Clone the repository to see the full change.
Modified lib/infra/api/src/api_error.rs lib/infra/api/src/api_error.rs +17 −0 View lib/infra/api/src/api_error.rs
@@ -55,6 +55,17 @@
5555 };
5656}
5757
58#[macro_export]
59macro_rules! page_error_conversion {
60 ($name:ident, $variant:ident) => {
61 impl From<$crate::pagination::PageError> for $name {
62 fn from(error: $crate::pagination::PageError) -> Self {
63 Self::$variant(error.to_string())
64 }
65 }
66 };
67}
68
5869/// Standard variant-to-variant mapping between two domain error enums sharing
5970/// the `BadRequest/Unauthorized/Forbidden/NotFound/Conflict/RateLimited/Internal`
6071/// shape; non-matching source variants fall through to `Internal`.
@@ -253,6 +264,12 @@
253264 }
254265 }
255266
267 impl From<$crate::pagination::PageError> for $name {
268 fn from(error: $crate::pagination::PageError) -> Self {
269 Self::bad_request(error.to_string())
270 }
271 }
272
256273 impl From<(::actix_web::http::StatusCode, &'static str)> for $name {
257274 fn from((status, message): (::actix_web::http::StatusCode, &'static str)) -> Self {
258275 ::tracing::error!(%status, %message, "unhandled (status, message) converted to 500");
Modified lib/infra/api/src/pagination.rs lib/infra/api/src/pagination.rs +77 −21 View lib/infra/api/src/pagination.rs
@@ -1,3 +1,5 @@
1use std::fmt;
2
13use serde::{Deserialize, Serialize};
24use utoipa::{IntoParams, ToSchema};
35
@@ -5,56 +7,110 @@
57pub const MAX_PAGE_LIMIT: u64 = 100;
68pub const MAX_OFFSET: u64 = 5000;
79
8#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
10#[derive(Debug, Clone, Copy, Default, Deserialize, IntoParams, ToSchema)]
11#[serde(deny_unknown_fields)]
912pub struct PageQuery {
1013 pub limit: Option<u64>,
1114 pub offset: Option<u64>,
12 pub sort: Option<String>,
1315}
1416
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 )
17impl PageQuery {
18 pub fn resolve(&self, default: u64, max: u64) -> Result<(u64, u64), PageError> {
19 resolve_bounds(self.limit, self.offset, default, max)
20 }
21
22 pub fn bounds(&self) -> Result<(u64, u64), PageError> {
23 self.resolve(DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT)
24 }
2025}
2126
22impl PageQuery {
23 pub fn limit(&self) -> u64 {
24 self.limit
25 .unwrap_or(DEFAULT_PAGE_LIMIT)
26 .clamp(1, MAX_PAGE_LIMIT)
27pub fn page_bounds(
28 limit: Option<u64>,
29 offset: Option<u64>,
30 default: u64,
31 max: u64,
32) -> Result<(u64, u64), PageError> {
33 resolve_bounds(limit, offset, default, max)
34}
35
36fn resolve_bounds(
37 limit: Option<u64>,
38 offset: Option<u64>,
39 default: u64,
40 max: u64,
41) -> Result<(u64, u64), PageError> {
42 let limit = limit.unwrap_or(default);
43 if limit == 0 || limit > max {
44 return Err(PageError(format!("limit must be between 1 and {max}")));
45 }
46 let offset = offset.unwrap_or(0);
47 if offset > MAX_OFFSET {
48 return Err(PageError(format!("offset must not exceed {MAX_OFFSET}")));
2749 }
50 Ok((limit, offset))
51}
52
53#[derive(Debug, Clone)]
54pub struct PageError(String);
2855
29 pub fn offset(&self) -> u64 {
30 self.offset.unwrap_or(0).clamp(0, MAX_OFFSET)
56impl PageError {
57 pub fn message(&self) -> &str {
58 &self.0
3159 }
60}
3261
33 pub fn page(&self) -> (u64, u64) {
34 (self.limit(), self.offset())
62impl fmt::Display for PageError {
63 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
64 formatter.write_str(&self.0)
3565 }
3666}
3767
68impl std::error::Error for PageError {}
69
3870#[derive(Debug, Clone, Serialize, ToSchema)]
3971pub struct Page<T> {
4072 pub items: Vec<T>,
41 pub total: i64,
73 pub total: u64,
4274 pub limit: u64,
4375 pub offset: u64,
4476}
4577
46pub fn paginate<T>(items: Vec<T>, page: &PageQuery) -> Page<T> {
47 let total = items.len() as i64;
48 let (limit, offset) = page.page();
78impl<T> Page<T> {
79 pub fn new(items: Vec<T>, total: u64, limit: u64, offset: u64) -> Self {
80 Self {
81 items,
82 total,
83 limit,
84 offset,
85 }
86 }
87}
88
89pub fn paginate<T>(items: Vec<T>, page: &PageQuery) -> Result<Page<T>, PageError> {
90 let (limit, offset) = page.bounds()?;
91 let total = items.len() as u64;
4992 let items = items
5093 .into_iter()
5194 .skip(offset as usize)
5295 .take(limit as usize)
5396 .collect();
54 Page {
97 Ok(Page {
5598 items,
5699 total,
57100 limit,
58101 offset,
102 })
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
106#[serde(rename_all = "snake_case")]
107pub enum SortOrder {
108 Asc,
109 Desc,
110}
111
112impl SortOrder {
113 pub fn is_desc(self) -> bool {
114 matches!(self, Self::Desc)
59115 }
60116}
Modified lib/service/auth/src/handlers/devices.rs lib/service/auth/src/handlers/devices.rs +8 −5 View lib/service/auth/src/handlers/devices.rs
@@ -60,15 +60,15 @@
6060 description = "按会话的用户代理归并出当前账号出现过的设备及其最近活跃时间。",
6161 params(PageQuery),
6262 responses(
63 (status = 200, description = "devices derived from active sessions", body = AppResponseData<Vec<DeviceDto>>),
64 (status = 401, description = "authentication required", body = AppResponseData<Vec<DeviceDto>>)
63 (status = 200, description = "devices derived from active sessions", body = AppResponseData<api::pagination::Page<DeviceDto>>),
64 (status = 401, description = "authentication required", body = AppResponseData<api::pagination::Page<DeviceDto>>)
6565 )
6666)]
6767pub async fn list_devices(
6868 State(service): State<AuthService>,
6969 session: Session,
7070 Query(query): Query<PageQuery>,
71) -> Result<AppResponseData<Vec<DeviceDto>>, ApiError> {
71) -> Result<AppResponseData<api::pagination::Page<DeviceDto>>, ApiError> {
7272 let uid = session::require_uid(&session)?;
7373 let listed = service
7474 .accounts
@@ -159,7 +159,8 @@
159159 }
160160
161161 rows.sort_by(|left, right| right.last_seen_at.cmp(&left.last_seen_at));
162 let (limit, offset) = query.page();
162 let (limit, offset) = query.bounds()?;
163 let total = rows.len() as u64;
163164 let devices = rows
164165 .into_iter()
165166 .skip(offset as usize)
@@ -174,5 +175,7 @@
174175 last_seen_at: row.last_seen_at,
175176 })
176177 .collect();
177 Ok(AppResponseData::ok(devices))
178 Ok(AppResponseData::ok(api::pagination::Page::new(
179 devices, total, limit, offset,
180 )))
178181}
Modified lib/service/auth/src/handlers/list_notifications.rs lib/service/auth/src/handlers/list_notifications.rs +15 −22 View lib/service/auth/src/handlers/list_notifications.rs
@@ -10,6 +10,7 @@
1010use ::session::share::session;
1111
1212#[derive(Debug, Clone, Copy, Default, Deserialize, IntoParams)]
13#[serde(deny_unknown_fields)]
1314pub struct ListNotificationsQuery {
1415 pub limit: Option<u64>,
1516 pub offset: Option<u64>,
@@ -18,6 +19,15 @@
1819 pub unread: Option<bool>,
1920}
2021
22impl ListNotificationsQuery {
23 fn page(&self) -> api::pagination::PageQuery {
24 api::pagination::PageQuery {
25 limit: self.limit,
26 offset: self.offset,
27 }
28 }
29}
30
2131#[utoipa::path(
2232 get,
2333 path = "/me/notifications",
@@ -36,28 +46,11 @@
3646 Query(query): Query<ListNotificationsQuery>,
3747) -> Result<AppResponseData<api::pagination::Page<NotificationDto>>, ApiError> {
3848 let uid = session::require_uid(&session)?;
39 let notifications = service
40 .list_notifications(
41 uid,
42 query.limit,
43 query.offset,
44 query.notification_type,
45 query.unread,
46 )
49 let notification_type = query.notification_type;
50 let unread = query.unread;
51 let page = service
52 .list_notifications(uid, query.page(), notification_type, unread)
4753 .await
4854 .map_err(ApiError::from)?;
49 let limit = query.limit.unwrap_or(50).min(100);
50 let offset = query.offset.unwrap_or(0);
51 let total = notifications.len() as i64;
52 let items: Vec<NotificationDto> = notifications
53 .into_iter()
54 .skip(offset as usize)
55 .take(limit as usize)
56 .collect();
57 Ok(AppResponseData::ok(api::pagination::Page {
58 items,
59 total,
60 limit,
61 offset,
62 }))
55 Ok(AppResponseData::ok(page))
6356}
Modified lib/service/auth/src/handlers/sessions.rs lib/service/auth/src/handlers/sessions.rs +2 −2 View lib/service/auth/src/handlers/sessions.rs
@@ -43,6 +43,7 @@
4343}
4444
4545#[derive(Debug, Deserialize, utoipa::IntoParams)]
46#[serde(deny_unknown_fields)]
4647pub struct SessionQuery {
4748 pub kind: Option<SessionKind>,
4849 pub include_revoked: Option<bool>,
@@ -55,7 +56,6 @@
5556 PageQuery {
5657 limit: self.limit,
5758 offset: self.offset,
58 sort: None,
5959 }
6060 }
6161}
@@ -142,7 +142,7 @@
142142 .map_err(|error| ApiError::internal(error.to_string()))?;
143143 let current_session = session.session_id();
144144 let sessions = session_list(listed, current_session.as_deref())?;
145 Ok(AppResponseData::ok(paginate(sessions, &query.page())))
145 Ok(AppResponseData::ok(paginate(sessions, &query.page())?))
146146}
147147
148148#[utoipa::path(
Modified lib/service/auth/src/services/notifications.rs lib/service/auth/src/services/notifications.rs +16 −14 View lib/service/auth/src/services/notifications.rs
@@ -1,3 +1,4 @@
1use api::pagination::{Page, PageQuery};
12use models::users::user_notifications;
23use models::users::user_notifications::NotificationType;
34use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
@@ -25,19 +26,13 @@
2526 pub async fn list_notifications(
2627 &self,
2728 uid: i64,
28 limit: Option<u64>,
29 offset: Option<u64>,
29 page: PageQuery,
3030 notification_type: Option<NotificationType>,
3131 unread: Option<bool>,
32 ) -> Result<Vec<NotificationDto>, AuthBusinessError> {
33 let max_limit = limit.unwrap_or(50).min(100);
34 let mut query = user_notifications::Entity::find()
35 .filter(user_notifications::Column::UserId.eq(uid))
36 .order_by_desc(user_notifications::Column::CreatedAt)
37 .limit(max_limit);
38 if let Some(offset) = offset {
39 query = query.offset(offset);
40 }
32 ) -> Result<Page<NotificationDto>, AuthBusinessError> {
33 let (limit, offset) = page.bounds()?;
34 let mut query =
35 user_notifications::Entity::find().filter(user_notifications::Column::UserId.eq(uid));
4136 if let Some(notification_type) = notification_type {
4237 query =
4338 query.filter(user_notifications::Column::NotificationType.eq(notification_type));
@@ -45,8 +40,14 @@
4540 if unread == Some(true) {
4641 query = query.filter(user_notifications::Column::ReadAt.is_null());
4742 }
48 let rows = query.all(self.db().read()).await?;
49 Ok(rows
43 let total = query.clone().count(self.db().read()).await?;
44 let rows = query
45 .order_by_desc(user_notifications::Column::CreatedAt)
46 .limit(limit)
47 .offset(offset)
48 .all(self.db().read())
49 .await?;
50 let items = rows
5051 .into_iter()
5152 .map(|row| NotificationDto {
5253 id: row.id,
@@ -58,7 +59,8 @@
5859 read_at: row.read_at,
5960 created_at: row.created_at,
6061 })
61 .collect())
62 .collect();
63 Ok(Page::new(items, total, limit, offset))
6264 }
6365
6466 pub async fn unread_notifications_count(&self, uid: i64) -> Result<u64, AuthBusinessError> {
Modified lib/service/auth/src/share/mod.rs lib/service/auth/src/share/mod.rs +1 −1 View lib/service/auth/src/share/mod.rs
@@ -14,7 +14,7 @@
1414pub mod user;
1515pub mod validate;
1616
17pub use types::{AgreementDoc, ListQuery, UserPublic};
17pub use types::{AgreementDoc, UserPublic};
1818pub mod auth_convert;
1919pub mod auth_error;
2020pub mod auth_store;
Modified lib/service/auth/src/share/types.rs lib/service/auth/src/share/types.rs +0 −24 View lib/service/auth/src/share/types.rs
@@ -1,9 +1,6 @@
11use serde::{Deserialize, Serialize};
22use utoipa::ToSchema;
33
4pub const DEFAULT_PAGE_SIZE: u64 = 20;
5pub const MAX_PAGE_SIZE: u64 = 100;
6
74#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
85pub struct UserPublic {
96 pub user_id: i64,
@@ -20,25 +17,4 @@
2017 pub version: String,
2118 pub title: String,
2219 pub content_md: String,
23}
24
25#[derive(Clone, Debug, Default, Deserialize, Serialize, ToSchema)]
26pub struct ListQuery {
27 pub page: Option<u64>,
28 pub page_size: Option<u64>,
29}
30
31impl ListQuery {
32 pub fn limit(&self) -> u64 {
33 self.page_size
34 .unwrap_or(DEFAULT_PAGE_SIZE)
35 .clamp(1, MAX_PAGE_SIZE)
36 }
37
38 pub fn offset(&self) -> u64 {
39 self.page
40 .unwrap_or(1)
41 .saturating_sub(1)
42 .saturating_mul(self.limit())
43 }
4420}
Modified lib/service/auth/src/errors.rs lib/service/auth/src/errors.rs +3 −0 View lib/service/auth/src/errors.rs
@@ -171,6 +171,8 @@
171171 }
172172}
173173
174api::page_error_conversion!(AuthError, BadRequest);
175
174176impl AuthError {
175177 pub fn internal(message: impl Into<String>) -> Self {
176178 Self::Internal(message.into())
@@ -381,6 +383,7 @@
381383api::db_error_conversions!(AuthBusinessError);
382384cache::redis_error_conversion!(AuthBusinessError);
383385api::json_payload_conversion!(AuthBusinessError);
386api::page_error_conversion!(AuthBusinessError, BadRequest);
384387
385388api::api_error! {
386389 log: "auth",
Modified lib/service/chat/src/handlers/bookmarks.rs lib/service/chat/src/handlers/bookmarks.rs +1 −1 View lib/service/chat/src/handlers/bookmarks.rs
@@ -30,7 +30,7 @@
3030 let bookmarks = service.list_bookmarks(uid, room_id).await?;
3131 Ok(AppResponseData::ok(api::pagination::paginate(
3232 bookmarks, &page,
33 )))
33 )?))
3434}
3535
3636#[utoipa::path(
Modified lib/service/chat/src/handlers/dms.rs lib/service/chat/src/handlers/dms.rs +1 −1 View lib/service/chat/src/handlers/dms.rs
@@ -52,7 +52,7 @@
5252) -> Result<AppResponseData<api::pagination::Page<DmDto>>, ApiError> {
5353 let uid = session::require_uid(&session)?;
5454 let dms = service.list_dms(uid).await?;
55 Ok(AppResponseData::ok(api::pagination::paginate(dms, &page)))
55 Ok(AppResponseData::ok(api::pagination::paginate(dms, &page)?))
5656}
5757
5858#[utoipa::path(
Modified lib/service/chat/src/handlers/drafts.rs lib/service/chat/src/handlers/drafts.rs +1 −1 View lib/service/chat/src/handlers/drafts.rs
@@ -80,7 +80,7 @@
8080 let drafts = service.list_my_drafts(uid).await?;
8181 Ok(AppResponseData::ok(api::pagination::paginate(
8282 drafts, &page,
83 )))
83 )?))
8484}
8585
8686#[utoipa::path(
Modified lib/service/chat/src/handlers/emojis.rs lib/service/chat/src/handlers/emojis.rs +1 −1 View lib/service/chat/src/handlers/emojis.rs
@@ -16,7 +16,7 @@
1616 description = "返回指定空间可用的自定义表情列表。",
1717 params(("space_name" = String, Path, description = "space name")),
1818 responses(
19 (status = 200, description = "emoji list", body = AppResponseData<api::pagination::Page<EmojiDto>>),
19 (status = 200, description = "emoji list", body = AppResponseData<Vec<EmojiDto>>),
2020 (status = 404, description = "not found", body = AppResponseData<NoData>),
2121 (status = 401, description = "authentication required", body = AppResponseData<NoData>)
2222 )
Modified lib/service/chat/src/handlers/read_state.rs lib/service/chat/src/handlers/read_state.rs +3 −3 View lib/service/chat/src/handlers/read_state.rs
@@ -5,7 +5,7 @@
55use uuid::Uuid;
66
77use crate::errors::ApiError;
8use crate::services::read_state::{MarkReadCmd, ReadStateDto, SpaceReadStatePage};
8use crate::services::read_state::{MarkReadCmd, ReadStateDto, SpaceReadStates};
99use crate::services::service::ChatService;
1010
1111#[utoipa::path(
@@ -62,7 +62,7 @@
6262 description = "汇总当前用户在一个空间内可见房间的未读状态。",
6363 params(("space_name" = String, Path, description = "space name")),
6464 responses(
65 (status = 200, description = "space read states", body = AppResponseData<SpaceReadStatePage>),
65 (status = 200, description = "space read states", body = AppResponseData<SpaceReadStates>),
6666 (status = 404, description = "not found", body = AppResponseData<NoData>),
6767 (status = 401, description = "authentication required", body = AppResponseData<NoData>)
6868 )
@@ -71,7 +71,7 @@
7171 State(service): State<ChatService>,
7272 session: Session,
7373 Path(space_name): Path<String>,
74) -> Result<AppResponseData<SpaceReadStatePage>, ApiError> {
74) -> Result<AppResponseData<SpaceReadStates>, ApiError> {
7575 let uid = session::require_uid(&session)?;
7676 let page = service.list_space_read_states(uid, &space_name).await?;
7777 Ok(AppResponseData::ok(page))
Modified lib/service/chat/src/handlers/threads.rs lib/service/chat/src/handlers/threads.rs +1 −1 View lib/service/chat/src/handlers/threads.rs
@@ -83,7 +83,7 @@
8383 let threads = service.list_my_threads(uid).await?;
8484 Ok(AppResponseData::ok(api::pagination::paginate(
8585 threads, &page,
86 )))
86 )?))
8787}
8888
8989#[utoipa::path(
Modified lib/service/chat/src/handlers/webhooks.rs lib/service/chat/src/handlers/webhooks.rs +1 −1 View lib/service/chat/src/handlers/webhooks.rs
@@ -57,7 +57,7 @@
5757) -> Result<AppResponseData<api::pagination::Page<WebhookDto>>, ApiError> {
5858 let uid = session::require_uid(&session)?;
5959 let rows = service.list_webhooks(uid, room_id).await?;
60 Ok(AppResponseData::ok(api::pagination::paginate(rows, &page)))
60 Ok(AppResponseData::ok(api::pagination::paginate(rows, &page)?))
6161}
6262
6363#[utoipa::path(
Modified lib/service/chat/src/services/messages.rs lib/service/chat/src/services/messages.rs +23 −30 View lib/service/chat/src/services/messages.rs
@@ -132,13 +132,7 @@
132132
133133pub use social::MessageEmbed as MessageEmbedDto;
134134
135#[derive(Debug, Clone, Serialize, ToSchema)]
136pub struct MessagePage {
137 pub items: Vec<MessageDto>,
138 pub oldest_seq: Option<i64>,
139 pub newest_seq: Option<i64>,
140 pub has_more: bool,
141}
135pub type MessagePage = api::pagination::Page<MessageDto>;
142136
143137#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
144138pub struct SendMessageCmd {
@@ -185,10 +179,12 @@
185179}
186180
187181#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
182#[serde(deny_unknown_fields)]
188183pub struct ListMessageQuery {
189184 pub before_seq: Option<i64>,
190185 pub after_seq: Option<i64>,
191186 pub limit: Option<u64>,
187 pub offset: Option<u64>,
192188 pub thread_id: Option<Uuid>,
193189 pub author_id: Option<i64>,
194190}
@@ -878,12 +874,12 @@
878874 ) -> Result<MessagePage, ChatError> {
879875 let _ctx = require_room_readable(self, room_id, caller).await?;
880876
881 let (limit, _) = api::pagination::page_bounds(
877 let (limit, offset) = api::pagination::page_bounds(
882878 q.limit,
883 None,
879 q.offset,
884880 self.settings.chat.default_pagination,
885881 self.settings.chat.max_pagination,
886 );
882 )?;
887883
888884 let mut select = messages::Entity::find()
889885 .filter(messages::Column::RoomId.eq(room_id))
@@ -906,38 +902,35 @@
906902 select = select.filter(messages::Column::AuthorId.eq(author_id));
907903 }
908904
909 let (rows, has_more) = if let Some(after_seq) = q.after_seq {
905 let (rows, total) = if let Some(after_seq) = q.after_seq {
906 let select = select.filter(messages::Column::Seq.gt(after_seq));
907 let total = select.clone().count(self.database.read()).await?;
910908 let rows = select
911 .filter(messages::Column::Seq.gt(after_seq))
912909 .order_by_asc(messages::Column::Seq)
913 .limit(limit + 1)
910 .limit(limit)
911 .offset(offset)
914912 .all(self.database.read())
915913 .await?;
916 let has_more = rows.len() as u64 > limit;
917 let rows: Vec<_> = rows.into_iter().take(limit as usize).collect();
918 (rows, has_more)
914 (rows, total)
919915 } else {
920 let mut desc = select.order_by_desc(messages::Column::Seq).limit(limit + 1);
916 let mut select = select;
921917 if let Some(before_seq) = q.before_seq {
922 desc = desc.filter(messages::Column::Seq.lt(before_seq));
918 select = select.filter(messages::Column::Seq.lt(before_seq));
923919 }
924 let mut rows = desc.all(self.database.read()).await?;
925 let has_more = rows.len() as u64 > limit;
926 rows.truncate(limit as usize);
920 let total = select.clone().count(self.database.read()).await?;
921 let mut rows = select
922 .order_by_desc(messages::Column::Seq)
923 .limit(limit)
924 .offset(offset)
925 .all(self.database.read())
926 .await?;
927927 rows.reverse();
928 (rows, has_more)
928 (rows, total)
929929 };
930930
931 let oldest_seq = rows.first().map(|m| m.seq);
932 let newest_seq = rows.last().map(|m| m.seq);
933931 let items = self.build_message_dto(rows, caller).await?;
934932
935 Ok(MessagePage {
936 items,
937 oldest_seq,
938 newest_seq,
939 has_more,
940 })
933 Ok(MessagePage::new(items, total, limit, offset))
941934 }
942935
943936 pub async fn get_message(
Modified lib/service/chat/src/services/read_state.rs lib/service/chat/src/services/read_state.rs +3 −3 View lib/service/chat/src/services/read_state.rs
@@ -26,7 +26,7 @@
2626}
2727
2828#[derive(Debug, Clone, Serialize, ToSchema)]
29pub struct SpaceReadStatePage {
29pub struct SpaceReadStates {
3030 pub items: Vec<ReadStateDto>,
3131}
3232
@@ -151,7 +151,7 @@
151151 &self,
152152 caller: i64,
153153 space_name: &str,
154 ) -> Result<SpaceReadStatePage, ChatError> {
154 ) -> Result<SpaceReadStates, ChatError> {
155155 let space = lookup_space_by_name(self, space_name).await?;
156156 require_space_member(self, &space, caller).await?;
157157
@@ -177,7 +177,7 @@
177177 }
178178 items.push(self.get_read_state(caller, room.id).await?);
179179 }
180 Ok(SpaceReadStatePage { items })
180 Ok(SpaceReadStates { items })
181181 }
182182
183183 pub async fn mark_thread_read(
Modified lib/service/chat/src/services/threads.rs lib/service/chat/src/services/threads.rs +2 −2 View lib/service/chat/src/services/threads.rs
@@ -253,13 +253,13 @@
253253 q.offset,
254254 self.settings.chat.default_pagination,
255255 self.settings.chat.max_pagination,
256 );
256 )?;
257257
258258 let mut select = threads::Entity::find().filter(threads::Column::RoomId.eq(room_id));
259259 if let Some(archived) = q.archived {
260260 select = select.filter(threads::Column::Archived.eq(archived));
261261 }
262 let total = select.clone().count(self.database.read()).await? as i64;
262 let total = select.clone().count(self.database.read()).await?;
263263 let rows = select
264264 .order_by_desc(threads::Column::LastReplyAt)
265265 .order_by_desc(threads::Column::CreatedAt)
Modified lib/service/chat/src/errors.rs lib/service/chat/src/errors.rs +1 −0 View lib/service/chat/src/errors.rs
@@ -34,6 +34,7 @@
3434storage::storage_error_conversion!(ChatError);
3535cache::redis_error_conversion!(ChatError);
3636api::json_payload_conversion!(ChatError);
37api::page_error_conversion!(ChatError, BadRequest);
3738api::access_error_conversion!(ChatError, BadRequest);
3839
3940impl From<dir::Error> for ChatError {
Modified lib/service/chat/src/openapi.rs lib/service/chat/src/openapi.rs +1 −3 View lib/service/chat/src/openapi.rs
@@ -97,7 +97,6 @@
9797 api::pagination::Page<crate::services::drafts::DraftDto>,
9898 api::pagination::Page<crate::services::webhooks::WebhookDto>,
9999 api::pagination::Page<crate::services::bookmarks::BookmarkDto>,
100 api::pagination::Page<crate::services::emojis::EmojiDto>,
101100 api::pagination::Page<crate::services::messages::MessageDto>,
102101 api::pagination::Page<crate::services::rooms::RoomBriefDto>,
103102 AppResponseData<api::rest::NoData>,
@@ -112,7 +111,6 @@
112111 models::chat::message_reminders::ReminderStatus,
113112 models::room::rooms::RoomKind,
114113 crate::services::messages::MessageDto,
115 crate::services::messages::MessagePage,
116114 crate::services::messages::MessageEditDto,
117115 crate::services::messages::SendMessageCmd,
118116 crate::services::messages::UpdateMessageCmd,
@@ -135,7 +133,7 @@
135133 crate::services::reactions::ReactionUserDto,
136134 crate::services::reactions::ReactionCmd,
137135 crate::services::read_state::ReadStateDto,
138 crate::services::read_state::SpaceReadStatePage,
136 crate::services::read_state::SpaceReadStates,
139137 crate::services::read_state::MarkReadCmd,
140138 crate::handlers::read_state::MarkThreadReadCmd,
141139 crate::services::attachments::AttachmentDto,
Modified lib/service/git/src/handler/branches/list.rs lib/service/git/src/handler/branches/list.rs +2 −2 View lib/service/git/src/handler/branches/list.rs
@@ -34,8 +34,8 @@
3434 .list_branches(&ctx)
3535 .await
3636 .map_err(GitApiError::from)?;
37 let total = all.len() as i64;
38 let (limit, offset) = page.page();
37 let total = all.len() as u64;
38 let (limit, offset) = page.bounds()?;
3939 let items = all
4040 .into_iter()
4141 .skip(offset as usize)
Modified lib/service/git/src/handler/commits/list.rs lib/service/git/src/handler/commits/list.rs +7 −7 View lib/service/git/src/handler/commits/list.rs
@@ -5,7 +5,7 @@
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
77use crate::service::GitService;
8use crate::service::commits::{CommitsPageDto, ListCommitsCmd};
8use crate::service::commits::{CommitDto, ListCommitsCmd};
99
1010#[utoipa::path(
1111 get,
@@ -16,14 +16,14 @@
1616 crate::handler::RepoPath,
1717 ("revision" = Option<String>, Query, description = "start revision"),
1818 ("path" = Option<String>, Query, description = "path filter"),
19 ("max_count" = Option<u32>, Query, description = "max commits"),
20 ("skip" = Option<u32>, Query, description = "skip commits before returning (offset)"),
19 ("limit" = Option<u64>, Query, description = "max commits per page (1-100)"),
20 ("offset" = Option<u64>, Query, description = "skip commits before returning (max 5000)"),
2121 ("since" = Option<String>, Query, description = "since date"),
2222 ("until" = Option<String>, Query, description = "until date"),
2323 ("first_parent" = bool, Query, description = "first parent only"),
2424 ),
2525 responses(
26 (status = 200, description = "list commits", body = AppResponseData<CommitsPageDto>)
26 (status = 200, description = "list commits", body = AppResponseData<api::pagination::Page<CommitDto>>)
2727 )
2828)]
2929pub async fn list_commits(
@@ -31,8 +31,8 @@
3131 session: Session,
3232 Path(path): Path<crate::handler::RepoPath>,
3333 Query(cmd): Query<ListCommitsCmd>,
34) -> Result<AppResponseData<CommitsPageDto>, ApiError> {
35 let (ctx, viewer) = handler::resolve_reader(
34) -> Result<AppResponseData<api::pagination::Page<CommitDto>>, ApiError> {
35 let (ctx, _viewer) = handler::resolve_reader(
3636 &service,
3737 &session,
3838 path.space_name.clone(),
@@ -40,7 +40,7 @@
4040 )
4141 .await?;
4242 let out = service
43 .list_commits(&ctx, cmd, viewer)
43 .list_commits(&ctx, cmd)
4444 .await
4545 .map_err(GitApiError::from)?;
4646 Ok(AppResponseData::ok(out))
Modified lib/service/git/src/handler/html/commits.rs lib/service/git/src/handler/html/commits.rs +15 −14 View lib/service/git/src/handler/html/commits.rs
@@ -20,7 +20,7 @@
2020 #[serde(default)]
2121 pub limit: Option<usize>,
2222 #[serde(default)]
23 pub skip: Option<usize>,
23 pub offset: Option<usize>,
2424}
2525
2626#[utoipa::path(
@@ -57,8 +57,8 @@
5757 } else {
5858 Some(PathBuf::from(clean_path.clone()))
5959 },
60 max_count: Some(limit as u32),
61 skip: q.skip.map(|s| s as u32),
60 limit: Some(limit as u64),
61 offset: q.offset.map(|s| s as u64),
6262 since: None,
6363 until: None,
6464 first_parent: false,
@@ -74,23 +74,24 @@
7474 return error::html_err(500, "internal");
7575 }
7676 };
77 let page = match service.list_commits(&repo_ctx, cmd, ctx.uid).await {
77 let page = match service.list_commits(&repo_ctx, cmd).await {
7878 Ok(p) => p,
7979 Err(e) => {
8080 tracing::warn!(error = %e, "commits list internal");
8181 return error::html_err(500, "internal");
8282 }
8383 };
84 let commits = page.commits;
84 let diff_view_style = service.user_diff_view_style(ctx.uid).await;
85 let commits = page.items;
8586
8687 let base =
8788 "git-commits w-full rounded-lg border border-zinc-200 bg-white overflow-hidden text-sm";
8889 let cls = archived_class(&ctx.dto, base);
8990 let space = ctx.space_name.clone();
9091 let repo = ctx.repo_name.clone();
91 let skip = page.skip as usize;
92 let skip = page.offset as usize;
9293 let older_skip = skip + limit;
93 let has_older = page.has_more;
94 let has_more = page.offset + (commits.len() as u64) < page.total;
9495
9596 let markup = html! {
9697 div class=(cls)
@@ -101,10 +102,10 @@
101102 data-path=(clean_path.as_str())
102103 data-count=(commits.len())
103104 data-limit=(page.limit)
104 data-skip=(page.skip)
105 data-skip=(page.offset)
105106 data-total=(page.total)
106 data-has-more=(if page.has_more { "true" } else { "false" })
107 data-diff-style=(page.diff_view_style)
107 data-has-more=(if has_more { "true" } else { "false" })
108 data-diff-style=(diff_view_style)
108109 data-archived=(if ctx.dto.is_archived { "true" } else { "false" }) {
109110 div class="flex items-center justify-between border-b border-zinc-200 bg-zinc-50 px-4 py-2 text-xs text-zinc-500 font-medium" {
110111 span { "Commit history" }
@@ -161,11 +162,11 @@
161162 }
162163 }
163164 }
164 @if page.total > page.limit.into() {
165 @if page.total > page.limit {
165166 div class="flex items-center justify-between border-t border-zinc-200 bg-zinc-50 px-4 py-2 text-xs" {
166167 @if skip > 0 {
167168 a class="rounded border border-zinc-200 bg-white px-3 py-1 font-medium text-zinc-700 hover:bg-zinc-100"
168 href=(format!("?revision={}&path={}&limit={}&skip={}", revision, clean_path, limit, skip.saturating_sub(limit))) {
169 href=(format!("?revision={}&path={}&limit={}&offset={}", revision, clean_path, limit, skip.saturating_sub(limit))) {
169170 "Newer"
170171 }
171172 } @else {
@@ -176,9 +177,9 @@
176177 span class="text-zinc-500 font-mono" {
177178 "page " (skip / limit + 1) " / " ((page.total as usize + limit - 1) / limit.max(1))
178179 }
179 @if has_older {
180 @if has_more {
180181 a class="rounded border border-zinc-200 bg-white px-3 py-1 font-medium text-zinc-700 hover:bg-zinc-100"
181 href=(format!("?revision={}&path={}&limit={}&skip={}", revision, clean_path, limit, older_skip)) {
182 href=(format!("?revision={}&path={}&limit={}&offset={}", revision, clean_path, limit, older_skip)) {
182183 "Older"
183184 }
184185 } @else {
Modified lib/service/git/src/handler/refs/branches.rs lib/service/git/src/handler/refs/branches.rs +6 −13 View lib/service/git/src/handler/refs/branches.rs
@@ -7,30 +7,22 @@
77use crate::service::GitService;
88use crate::service::refs::BranchInfoDto;
99
10#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
11pub struct RefsBranchesQuery {
12 #[serde(default)]
13 pub skip: Option<u64>,
14 #[serde(default)]
15 pub limit: Option<u64>,
16}
17
1810#[utoipa::path(
1911 get,
2012 path = "/repos/{space_name}/{repo_name}/refs/branches",
2113 tag = "refs",
2214 description = "返回分支引用摘要及保护标记,供内部 Git 流程分页使用。",
23 params(crate::handler::RepoPath, RefsBranchesQuery),
15 params(crate::handler::RepoPath, api::pagination::PageQuery),
2416 responses(
25 (status = 200, description = "list branch refs", body = AppResponseData<Vec<BranchInfoDto>>)
17 (status = 200, description = "list branch refs", body = AppResponseData<api::pagination::Page<BranchInfoDto>>)
2618 )
2719)]
2820pub async fn refs_branches(
2921 State(service): State<GitService>,
3022 session: Session,
3123 Path(path): Path<crate::handler::RepoPath>,
32 Query(query): Query<RefsBranchesQuery>,
33) -> Result<AppResponseData<Vec<BranchInfoDto>>, ApiError> {
24 Query(query): Query<api::pagination::PageQuery>,
25) -> Result<AppResponseData<api::pagination::Page<BranchInfoDto>>, ApiError> {
3426 let (ctx, _) = handler::resolve_reader(
3527 &service,
3628 &session,
@@ -38,8 +30,9 @@
3830 path.repo_name.clone(),
3931 )
4032 .await?;
33 let (limit, offset) = query.bounds()?;
4134 let out = service
42 .refs_branches(&ctx, query.skip.unwrap_or(0), query.limit.unwrap_or(0))
35 .refs_branches(&ctx, limit, offset)
4336 .await
4437 .map_err(GitApiError::from)?;
4538 Ok(AppResponseData::ok(out))
Modified lib/service/git/src/handler/repos/list.rs lib/service/git/src/handler/repos/list.rs +3 −3 View lib/service/git/src/handler/repos/list.rs
@@ -4,7 +4,7 @@
44
55use crate::errors::{ApiError, GitApiError};
66use crate::service::GitService;
7use crate::service::repos::RepoSummaryDto;
7use crate::service::repos::{RepoListQuery, RepoSummaryDto};
88
99#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1010pub struct SpacePath {
@@ -16,7 +16,7 @@
1616 path = "/repos/{space_name}",
1717 tag = "repos",
1818 description = "分页列出空间中当前调用者可见的仓库,并支持名称、时间或星数排序。",
19 params(SpacePath, api::pagination::PageQuery),
19 params(SpacePath, RepoListQuery),
2020 responses(
2121 (status = 200, description = "list repos", body = AppResponseData<api::pagination::Page<RepoSummaryDto>>)
2222 )
@@ -25,7 +25,7 @@
2525 State(service): State<GitService>,
2626 session: Session,
2727 Path(path): Path<SpacePath>,
28 Query(page): Query<api::pagination::PageQuery>,
28 Query(page): Query<RepoListQuery>,
2929) -> Result<AppResponseData<api::pagination::Page<RepoSummaryDto>>, ApiError> {
3030 let caller = session.uid();
3131 let out = service
Modified lib/service/git/src/handler/tags/list.rs lib/service/git/src/handler/tags/list.rs +2 −2 View lib/service/git/src/handler/tags/list.rs
@@ -31,8 +31,8 @@
3131 )
3232 .await?;
3333 let all = service.list_tags(&ctx).await.map_err(GitApiError::from)?;
34 let total = all.len() as i64;
35 let (limit, offset) = page.page();
34 let total = all.len() as u64;
35 let (limit, offset) = page.bounds()?;
3636 let items = all
3737 .into_iter()
3838 .skip(offset as usize)
Modified lib/service/git/src/handler/licenses.rs lib/service/git/src/handler/licenses.rs +6 −15 View lib/service/git/src/handler/licenses.rs
@@ -1,4 +1,5 @@
11use api::http::{Json, Path, Query, State};
2use api::pagination::{Page, PageQuery};
23use api::rest::AppResponseData;
34use serde::Deserialize;
45use utoipa::IntoParams;
@@ -12,31 +13,21 @@
1213 pub name: String,
1314}
1415
15#[derive(Debug, Clone, Deserialize, IntoParams)]
16pub struct LicenseListQuery {
17 pub limit: Option<u64>,
18 pub offset: Option<u64>,
19}
20
2116#[utoipa::path(
2217 get,
2318 path = "/licenses",
2419 tag = "licenses",
2520 description = "分页列出系统登记的许可证。",
26 params(LicenseListQuery),
21 params(PageQuery),
2722 responses(
28 (status = 200, description = "list licenses", body = AppResponseData<Vec<LicenseDto>>)
23 (status = 200, description = "list licenses", body = AppResponseData<Page<LicenseDto>>)
2924 )
3025)]
3126pub async fn list_licenses(
3227 State(service): State<GitService>,
33 Query(q): Query<LicenseListQuery>,
34) -> Result<AppResponseData<Vec<LicenseDto>>, ApiError> {
35 let limit = q
36 .limit
37 .unwrap_or(api::pagination::DEFAULT_PAGE_LIMIT)
38 .clamp(1, api::pagination::MAX_PAGE_LIMIT);
39 let offset = q.offset.unwrap_or(0).clamp(0, api::pagination::MAX_OFFSET);
28 Query(q): Query<PageQuery>,
29) -> Result<AppResponseData<Page<LicenseDto>>, ApiError> {
30 let (limit, offset) = q.bounds()?;
4031 let out = service
4132 .list_licenses(limit, offset)
4233 .await
Modified lib/service/git/src/handler/stars.rs lib/service/git/src/handler/stars.rs +5 −12 View lib/service/git/src/handler/stars.rs
@@ -15,12 +15,6 @@
1515 pub repo_name: String,
1616}
1717
18#[derive(Debug, Clone, Deserialize, IntoParams)]
19pub struct StarQuery {
20 pub limit: Option<u64>,
21 pub offset: Option<u64>,
22}
23
2418#[utoipa::path(
2519 put,
2620 path = "/repos/{space_name}/{repo_name}/star",
@@ -86,17 +80,17 @@
8680 path = "/repos/{space_name}/{repo_name}/stargazers",
8781 tag = "repos",
8882 description = "分页列出最近为仓库加星的用户。",
89 params(RepoPath, StarQuery),
83 params(RepoPath, api::pagination::PageQuery),
9084 responses(
91 (status = 200, description = "list stargazers", body = AppResponseData<Vec<StargazerDto>>)
85 (status = 200, description = "list stargazers", body = AppResponseData<api::pagination::Page<StargazerDto>>)
9286 )
9387)]
9488pub async fn list_stargazers(
9589 State(service): State<GitService>,
9690 session: Session,
9791 Path(path): Path<RepoPath>,
98 Query(q): Query<StarQuery>,
99) -> Result<AppResponseData<Vec<StargazerDto>>, ApiError> {
92 Query(q): Query<api::pagination::PageQuery>,
93) -> Result<AppResponseData<api::pagination::Page<StargazerDto>>, ApiError> {
10094 let ctx = resolve_reader(
10195 &service,
10296 &session,
@@ -105,8 +99,7 @@
10599 )
106100 .await?
107101 .0;
108 let limit = q.limit.unwrap_or(50).clamp(1, 100);
109 let offset = q.offset.unwrap_or(0).clamp(0, api::pagination::MAX_OFFSET);
102 let (limit, offset) = q.bounds()?;
110103 let out = service
111104 .list_stargazers(&ctx, limit, offset)
112105 .await
Modified lib/service/git/src/handler/topics.rs lib/service/git/src/handler/topics.rs +10 −18 View lib/service/git/src/handler/topics.rs
@@ -14,12 +14,6 @@
1414 pub name: String,
1515}
1616
17#[derive(Debug, Clone, Deserialize, IntoParams)]
18pub struct TopicListQuery {
19 pub limit: Option<u64>,
20 pub offset: Option<u64>,
21}
22
2317#[derive(Debug, Clone, Deserialize, IntoParams, utoipa::ToSchema)]
2418pub struct RepoTopicPath {
2519 pub space_name: String,
@@ -58,17 +52,16 @@
5852 path = "/topics",
5953 tag = "topics",
6054 description = "分页列出系统全部标签。",
61 params(TopicListQuery),
55 params(api::pagination::PageQuery),
6256 responses(
63 (status = 200, description = "list topics", body = AppResponseData<Vec<TopicDto>>)
57 (status = 200, description = "list topics", body = AppResponseData<api::pagination::Page<TopicDto>>)
6458 )
6559)]
6660pub async fn list_topics(
6761 State(service): State<GitService>,
68 Query(q): Query<TopicListQuery>,
69) -> Result<AppResponseData<Vec<TopicDto>>, ApiError> {
70 let limit = q.limit.unwrap_or(50).clamp(1, 100);
71 let offset = q.offset.unwrap_or(0).clamp(0, api::pagination::MAX_OFFSET);
62 Query(q): Query<api::pagination::PageQuery>,
63) -> Result<AppResponseData<api::pagination::Page<TopicDto>>, ApiError> {
64 let (limit, offset) = q.bounds()?;
7265 let out = service
7366 .list_topics(limit, offset)
7467 .await
@@ -102,18 +95,17 @@
10295 path = "/topics/{name}/repos",
10396 tag = "topics",
10497 description = "分页列出关联某标签的仓库记录。",
105 params(TopicNamePath, TopicListQuery),
98 params(TopicNamePath, api::pagination::PageQuery),
10699 responses(
107 (status = 200, description = "repos using topic", body = AppResponseData<Vec<TopicRepoDto>>)
100 (status = 200, description = "repos using topic", body = AppResponseData<api::pagination::Page<TopicRepoDto>>)
108101 )
109102)]
110103pub async fn list_repos_by_topic(
111104 State(service): State<GitService>,
112105 Path(p): Path<TopicNamePath>,
113 Query(q): Query<TopicListQuery>,
114) -> Result<AppResponseData<Vec<TopicRepoDto>>, ApiError> {
115 let limit = q.limit.unwrap_or(50).clamp(1, 100);
116 let offset = q.offset.unwrap_or(0).clamp(0, api::pagination::MAX_OFFSET);
106 Query(q): Query<api::pagination::PageQuery>,
107) -> Result<AppResponseData<api::pagination::Page<TopicRepoDto>>, ApiError> {
108 let (limit, offset) = q.bounds()?;
117109 let out = service
118110 .list_repos_by_topic(&p.name, limit, offset)
119111 .await
Modified lib/service/git/src/handler/watches.rs lib/service/git/src/handler/watches.rs +10 −4 View lib/service/git/src/handler/watches.rs
@@ -11,12 +11,19 @@
1111use crate::service::watches::{RepoWatchDto, UpsertWatchCmd, WatcherDto};
1212
1313#[derive(Debug, Clone, Deserialize, IntoParams)]
14#[serde(deny_unknown_fields)]
1415pub struct WatchQuery {
1516 pub limit: Option<u64>,
1617 pub offset: Option<u64>,
1718 pub mode: Option<WatchMode>,
1819}
1920
21impl WatchQuery {
22 fn bounds(&self) -> Result<(u64, u64), api::pagination::PageError> {
23 api::pagination::page_bounds(self.limit, self.offset, 50, 100)
24 }
25}
26
2027#[utoipa::path(
2128 put,
2229 path = "/repos/{space_name}/{repo_name}/watch",
@@ -56,7 +63,7 @@
5663 description = "分页列出仓库关注者,并可按关注模式过滤。",
5764 params(RepoPath, WatchQuery),
5865 responses(
59 (status = 200, description = "list watchers", body = AppResponseData<Vec<WatcherDto>>)
66 (status = 200, description = "list watchers", body = AppResponseData<api::pagination::Page<WatcherDto>>)
6067 )
6168)]
6269pub async fn list_watchers(
@@ -64,7 +71,7 @@
6471 session: Session,
6572 Path(path): Path<RepoPath>,
6673 Query(q): Query<WatchQuery>,
67) -> Result<AppResponseData<Vec<WatcherDto>>, ApiError> {
74) -> Result<AppResponseData<api::pagination::Page<WatcherDto>>, ApiError> {
6875 let ctx = resolve_reader(
6976 &service,
7077 &session,
@@ -73,8 +80,7 @@
7380 )
7481 .await?
7582 .0;
76 let limit = q.limit.unwrap_or(50).clamp(1, 100);
77 let offset = q.offset.unwrap_or(0).clamp(0, api::pagination::MAX_OFFSET);
83 let (limit, offset) = q.bounds()?;
7884 let out = service
7985 .list_watchers(&ctx, q.mode, limit, offset)
8086 .await
Modified lib/service/git/src/service/code_comments.rs lib/service/git/src/service/code_comments.rs +3 −3 View lib/service/git/src/service/code_comments.rs
@@ -56,6 +56,7 @@
5656}
5757
5858#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
59#[serde(deny_unknown_fields)]
5960pub struct ListCodeCommentsQuery {
6061 #[serde(default)]
6162 pub path: Option<String>,
@@ -75,8 +76,7 @@
7576 ctx: &GitContext,
7677 query: ListCodeCommentsQuery,
7778 ) -> Result<CodeCommentPage, GitError> {
78 let limit = query.limit.unwrap_or(50).clamp(1, 200);
79 let offset = query.offset.unwrap_or(0);
79 let (limit, offset) = api::pagination::page_bounds(query.limit, query.offset, 50, 200)?;
8080 let mut select =
8181 code_comment::Entity::find().filter(code_comment::Column::RepoId.eq(ctx.model.id));
8282 if let Some(path) = query
@@ -106,7 +106,7 @@
106106 select = select.filter(code_comment::Column::ResolvedAt.is_null());
107107 }
108108 }
109 let total = select.clone().count(self.db_read()).await? as i64;
109 let total = select.clone().count(self.db_read()).await?;
110110 let rows = select
111111 .order_by_asc(code_comment::Column::Path)
112112 .order_by_asc(code_comment::Column::Line)
Modified lib/service/git/src/service/commits.rs lib/service/git/src/service/commits.rs +11 −29 View lib/service/git/src/service/commits.rs
@@ -15,20 +15,23 @@
1515use crate::service::cache::{self, IMMUTABLE_TTL};
1616use crate::share::errors::{GitError, map_baby};
1717
18use api::pagination::Page;
19
1820fn default_revision() -> String {
1921 "HEAD".to_string()
2022}
2123
2224#[derive(Debug, Clone, Deserialize)]
25#[serde(deny_unknown_fields)]
2326pub struct ListCommitsCmd {
2427 #[serde(default = "default_revision")]
2528 pub revision: String,
2629 #[serde(default)]
2730 pub path: Option<PathBuf>,
2831 #[serde(default)]
29 pub max_count: Option<u32>,
32 pub limit: Option<u64>,
3033 #[serde(default)]
31 pub skip: Option<u32>,
34 pub offset: Option<u64>,
3235 #[serde(default)]
3336 pub since: Option<String>,
3437 #[serde(default)]
@@ -68,16 +71,6 @@
6871 }
6972}
7073
71#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
72pub struct CommitsPageDto {
73 pub commits: Vec<CommitDto>,
74 pub total: u64,
75 pub skip: u32,
76 pub limit: u32,
77 pub has_more: bool,
78 pub diff_view_style: String,
79}
80
8174#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
8275#[serde(rename_all = "lowercase")]
8376pub enum CommitStatusDtoState {
@@ -172,11 +165,9 @@
172165 &self,
173166 ctx: &GitContext,
174167 cmd: ListCommitsCmd,
175 viewer: Option<i64>,
176 ) -> Result<CommitsPageDto, GitError> {
168 ) -> Result<Page<CommitDto>, GitError> {
177169 let baby = self.baby(&ctx);
178 let limit = cmd.max_count.unwrap_or(50);
179 let skip = cmd.skip.unwrap_or(0);
170 let (limit, offset) = api::pagination::page_bounds(cmd.limit, cmd.offset, 50, 100)?;
180171 let rev_key = if let Some((after, before)) = cmd.revision.split_once("..") {
181172 let a = baby.rev_parse(after).await.map_err(map_baby)?;
182173 let b = baby.rev_parse(before).await.map_err(map_baby)?;
@@ -197,7 +188,7 @@
197188 "repo.{}.commits_page.{}:{}:{}:{}:{}:{}:{}",
198189 ctx.model.id,
199190 rev_oid,
200 skip,
191 offset,
201192 limit,
202193 path_str,
203194 cmd.since.as_deref().unwrap_or(""),
@@ -207,8 +198,8 @@
207198 let opts = ListCommitsOptions {
208199 revision: cmd.revision,
209200 path: cmd.path,
210 max_count: Some(limit),
211 skip: Some(skip),
201 max_count: Some(limit as u32),
202 skip: Some(offset as u32),
212203 since: cmd.since,
213204 until: cmd.until,
214205 first_parent: cmd.first_parent,
@@ -226,16 +217,7 @@
226217 Ok((commits, total))
227218 })
228219 .await?;
229 let has_more = (skip as u64 + commits.len() as u64) < total;
230 let diff_view_style = self.user_diff_view_style(viewer).await;
231 Ok(CommitsPageDto {
232 commits,
233 total,
234 skip,
235 limit,
236 has_more,
237 diff_view_style,
238 })
220 Ok(Page::new(commits, total, limit, offset))
239221 }
240222
241223 pub async fn user_diff_view_style(&self, viewer: Option<i64>) -> String {
Modified lib/service/git/src/service/licenses.rs lib/service/git/src/service/licenses.rs +6 −3 View lib/service/git/src/service/licenses.rs
@@ -1,5 +1,6 @@
1use api::pagination::Page;
12use models::repo::repo_license;
2use sea_orm::{ActiveModelTrait, EntityTrait, QueryOrder, QuerySelect, Set};
3use sea_orm::{ActiveModelTrait, EntityTrait, PaginatorTrait, QueryOrder, QuerySelect, Set};
34use serde::{Deserialize, Serialize};
45use time::OffsetDateTime;
56use utoipa::ToSchema;
@@ -53,14 +54,16 @@
5354 &self,
5455 limit: u64,
5556 offset: u64,
56 ) -> Result<Vec<LicenseDto>, GitError> {
57 ) -> Result<Page<LicenseDto>, GitError> {
58 let total = repo_license::Entity::find().count(self.db_read()).await?;
5759 let rows = repo_license::Entity::find()
5860 .order_by_asc(repo_license::Column::Name)
5961 .limit(limit)
6062 .offset(offset)
6163 .all(self.db_read())
6264 .await?;
63 Ok(rows.into_iter().map(Into::into).collect())
65 let items = rows.into_iter().map(Into::into).collect();
66 Ok(Page::new(items, total, limit, offset))
6467 }
6568
6669 pub async fn upsert_license(
Modified lib/service/git/src/service/refs.rs lib/service/git/src/service/refs.rs +11 −9 View lib/service/git/src/service/refs.rs
@@ -1,6 +1,8 @@
11use crate::rpcgit::refs::types::{ListRefsOptions, RefInfo};
22use serde::Serialize;
33
4use api::pagination::Page;
5
46use crate::context::facade::GitContext;
57use crate::service::GitService;
68use crate::share::errors::{GitError, map_baby};
@@ -92,9 +94,9 @@
9294 pub async fn refs_branches(
9395 &self,
9496 ctx: &GitContext,
95 skip: u64,
9697 limit: u64,
97 ) -> Result<Vec<BranchInfoDto>, GitError> {
98 offset: u64,
99 ) -> Result<Page<BranchInfoDto>, GitError> {
98100 let baby = self.baby(&ctx);
99101 let branches = baby
100102 .list_branches(crate::rpcgit::branch::ListBranchesOptions::default())
@@ -111,13 +113,13 @@
111113 })
112114 .collect();
113115 out.sort_by(|a, b| a.name.cmp(&b.name));
114 if let Some(start) = skip.checked_sub(1) {
115 out = out.into_iter().skip(start as usize).collect();
116 }
117 if limit > 0 {
118 out.truncate(limit as usize);
119 }
120 Ok(out)
116 let total = out.len() as u64;
117 let items = out
118 .into_iter()
119 .skip(offset as usize)
120 .take(limit as usize)
121 .collect();
122 Ok(Page::new(items, total, limit, offset))
121123 }
122124
123125 pub async fn refs_update(
Modified lib/service/git/src/service/releases.rs lib/service/git/src/service/releases.rs +3 −3 View lib/service/git/src/service/releases.rs
@@ -38,6 +38,7 @@
3838pub type ReleasePage = Page<ReleaseDto>;
3939
4040#[derive(Debug, Clone, Deserialize, IntoParams)]
41#[serde(deny_unknown_fields)]
4142pub struct ListReleasesQuery {
4243 #[serde(default)]
4344 pub limit: Option<u64>,
@@ -80,14 +81,13 @@
8081 ctx: &GitContext,
8182 query: ListReleasesQuery,
8283 ) -> Result<ReleasePage, GitError> {
83 let limit = query.limit.unwrap_or(20).clamp(1, 100);
84 let offset = query.offset.unwrap_or(0);
84 let (limit, offset) = api::pagination::page_bounds(query.limit, query.offset, 20, 100)?;
8585 let mut select =
8686 repo_release::Entity::find().filter(repo_release::Column::RepoId.eq(ctx.model.id));
8787 if let Some(draft) = query.draft {
8888 select = select.filter(repo_release::Column::IsDraft.eq(draft));
8989 }
90 let total = select.clone().count(self.db_read()).await? as i64;
90 let total = select.clone().count(self.db_read()).await?;
9191 let rows = select
9292 .order_by_desc(repo_release::Column::CreatedAt)
9393 .offset(offset)
Modified lib/service/git/src/service/repos.rs lib/service/git/src/service/repos.rs +73 −16 View lib/service/git/src/service/repos.rs
@@ -15,8 +15,46 @@
1515use crate::share::errors::{GitError, map_baby};
1616use crate::share::validate::{validate_branch_name, validate_repo_name};
1717
18use api::pagination::SortOrder;
19
1820pub const DEFAULT_BRANCH: &str = "main";
1921
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, utoipa::ToSchema)]
23#[serde(rename_all = "snake_case")]
24pub enum RepoSort {
25 CreatedAt,
26 UpdatedAt,
27 Stars,
28 Name,
29}
30
31impl RepoSort {
32 fn default_order(self) -> SortOrder {
33 match self {
34 Self::CreatedAt | Self::UpdatedAt | Self::Name => SortOrder::Asc,
35 Self::Stars => SortOrder::Desc,
36 }
37 }
38}
39
40#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
41#[serde(deny_unknown_fields)]
42pub struct RepoListQuery {
43 pub limit: Option<u64>,
44 pub offset: Option<u64>,
45 pub sort: Option<RepoSort>,
46 pub order: Option<SortOrder>,
47}
48
49impl RepoListQuery {
50 fn page(&self) -> api::pagination::PageQuery {
51 api::pagination::PageQuery {
52 limit: self.limit,
53 offset: self.offset,
54 }
55 }
56}
57
2058/// 仓库页签中可由设置重排序的中间页签(代码固定最前、设置固定最后)。
2159pub const REORDERABLE_REPO_TABS: [&str; 4] = ["commits", "pulls", "branches", "releases"];
2260
@@ -457,8 +495,9 @@
457495 &self,
458496 space_name: String,
459497 caller: Option<i64>,
460 page: api::pagination::PageQuery,
498 q: RepoListQuery,
461499 ) -> Result<api::pagination::Page<RepoSummaryDto>, GitError> {
500 let (limit, offset) = q.page().bounds()?;
462501 let space = self.lookup_space(&space_name).await?;
463502 let dirs = dirs::Entity::find()
464503 .filter(dirs::Column::SpaceId.eq(space.id()))
@@ -467,13 +506,37 @@
467506 .await?;
468507 let dir_ids: Vec<Uuid> = dirs.iter().map(|d| d.id).collect();
469508 let mut sel = repos::Entity::find().filter(repos::Column::DirId.is_in(dir_ids));
470 sel = match page.sort.as_deref() {
471 Some("created_at") => sel.order_by_asc(repos::Column::CreatedAt),
472 Some("-updated_at") => sel.order_by_desc(repos::Column::UpdatedAt),
473 Some("updated_at") => sel.order_by_asc(repos::Column::UpdatedAt),
474 Some("stars") => sel.order_by_desc(repos::Column::NumsStar),
475 Some("name") => sel.order_by_asc(repos::Column::Name),
476 _ => sel.order_by_asc(repos::Column::CreatedAt),
509 let sort = q.sort.unwrap_or(RepoSort::CreatedAt);
510 let descending = q.order.unwrap_or(sort.default_order()) == SortOrder::Desc;
511 sel = match sort {
512 RepoSort::CreatedAt => {
513 if descending {
514 sel.order_by_desc(repos::Column::CreatedAt)
515 } else {
516 sel.order_by_asc(repos::Column::CreatedAt)
517 }
518 }
519 RepoSort::UpdatedAt => {
520 if descending {
521 sel.order_by_desc(repos::Column::UpdatedAt)
522 } else {
523 sel.order_by_asc(repos::Column::UpdatedAt)
524 }
525 }
526 RepoSort::Stars => {
527 if descending {
528 sel.order_by_desc(repos::Column::NumsStar)
529 } else {
530 sel.order_by_asc(repos::Column::NumsStar)
531 }
532 }
533 RepoSort::Name => {
534 if descending {
535 sel.order_by_desc(repos::Column::Name)
536 } else {
537 sel.order_by_asc(repos::Column::Name)
538 }
539 }
477540 };
478541 let rows = sel.all(self.db_read()).await?;
479542
@@ -495,19 +558,13 @@
495558 .filter(|r| !r.is_private || owner_view || accessible.contains(&r.id))
496559 .map(RepoSummaryDto::from)
497560 .collect();
498 let total = visible.len() as i64;
499 let (limit, offset) = page.page();
561 let total = visible.len() as u64;
500562 let items = visible
501563 .into_iter()
502564 .skip(offset as usize)
503565 .take(limit as usize)
504566 .collect();
505 Ok(api::pagination::Page {
506 items,
507 total,
508 limit,
509 offset,
510 })
567 Ok(api::pagination::Page::new(items, total, limit, offset))
511568 }
512569
513570 pub async fn update_repo(
Modified lib/service/git/src/service/stars.rs lib/service/git/src/service/stars.rs +10 −10 View lib/service/git/src/service/stars.rs
@@ -1,7 +1,8 @@
1use api::pagination::Page;
12use models::repo::{repo_star, repos};
23use sea_orm::{
3 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set,
4 TransactionTrait, Unchanged,
4 ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
5 QuerySelect, Set, TransactionTrait, Unchanged,
56};
67use serde::{Deserialize, Serialize};
78use time::OffsetDateTime;
@@ -120,20 +121,18 @@
120121 ctx: &GitContext,
121122 limit: u64,
122123 offset: u64,
123 ) -> Result<Vec<StargazerDto>, GitError> {
124 let rows = repo_star::Entity::find()
125 .filter(repo_star::Column::RepoId.eq(ctx.model.id))
124 ) -> Result<Page<StargazerDto>, GitError> {
125 let select = repo_star::Entity::find().filter(repo_star::Column::RepoId.eq(ctx.model.id));
126 let total = select.clone().count(self.db_read()).await?;
127 let rows = select
126128 .order_by_desc(repo_star::Column::CreatedAt)
127129 .limit(limit)
128130 .offset(offset)
129131 .all(self.db_read())
130132 .await?;
131 if rows.is_empty() {
132 return Ok(Vec::new());
133 }
134133 let user_ids: Vec<i64> = rows.iter().map(|r| r.user_id).collect();
135134 let by_id = auth::users::load_public_users(&self.accounts, &user_ids).await?;
136 Ok(rows
135 let items = rows
137136 .into_iter()
138137 .filter_map(|row| {
139138 by_id.get(&row.user_id).map(|u| StargazerDto {
@@ -148,6 +147,7 @@
148147 starred_at: row.created_at,
149148 })
150149 })
151 .collect())
150 .collect();
151 Ok(Page::new(items, total, limit, offset))
152152 }
153153}
Modified lib/service/git/src/service/topics.rs lib/service/git/src/service/topics.rs +15 −11 View lib/service/git/src/service/topics.rs
@@ -1,6 +1,8 @@
1use api::pagination::Page;
12use models::repo::{repo_relation_topic, repo_topic, repos};
23use sea_orm::{
3 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set,
4 ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
5 QuerySelect, Set,
46};
57use serde::{Deserialize, Serialize};
68use time::OffsetDateTime;
@@ -78,14 +80,16 @@
7880 Ok(row.into())
7981 }
8082
81 pub async fn list_topics(&self, limit: u64, offset: u64) -> Result<Vec<TopicDto>, GitError> {
83 pub async fn list_topics(&self, limit: u64, offset: u64) -> Result<Page<TopicDto>, GitError> {
84 let total = repo_topic::Entity::find().count(self.db_read()).await?;
8285 let rows = repo_topic::Entity::find()
8386 .order_by_asc(repo_topic::Column::Name)
8487 .limit(limit)
8588 .offset(offset)
8689 .all(self.db_read())
8790 .await?;
88 Ok(rows.into_iter().map(Into::into).collect())
91 let items = rows.into_iter().map(Into::into).collect();
92 Ok(Page::new(items, total, limit, offset))
8993 }
9094
9195 pub async fn add_topic_to_repo(
@@ -166,27 +170,26 @@
166170 topic_name: &str,
167171 limit: u64,
168172 offset: u64,
169 ) -> Result<Vec<TopicRepoDto>, GitError> {
173 ) -> Result<Page<TopicRepoDto>, GitError> {
170174 let topic = repo_topic::Entity::find()
171175 .filter(repo_topic::Column::Name.eq(topic_name))
172176 .one(self.db_read())
173177 .await?
174178 .ok_or(GitError::NotFound)?;
175 let relations = repo_relation_topic::Entity::find()
176 .filter(repo_relation_topic::Column::TopicId.eq(topic.id))
179 let select = repo_relation_topic::Entity::find()
180 .filter(repo_relation_topic::Column::TopicId.eq(topic.id));
181 let total = select.clone().count(self.db_read()).await?;
182 let relations = select
177183 .limit(limit)
178184 .offset(offset)
179185 .all(self.db_read())
180186 .await?;
181 if relations.is_empty() {
182 return Ok(Vec::new());
183 }
184187 let repo_ids: Vec<Uuid> = relations.iter().map(|r| r.repo_id).collect();
185188 let rows = repos::Entity::find()
186189 .filter(repos::Column::Id.is_in(repo_ids))
187190 .all(self.db_read())
188191 .await?;
189 Ok(rows
192 let items = rows
190193 .into_iter()
191194 .map(|r| TopicRepoDto {
192195 repo_id: r.id,
@@ -194,6 +197,7 @@
194197 space_name: String::new(),
195198 is_private: r.is_private,
196199 })
197 .collect())
200 .collect();
201 Ok(Page::new(items, total, limit, offset))
198202 }
199203}
Modified lib/service/git/src/service/watches.rs lib/service/git/src/service/watches.rs +17 −15 View lib/service/git/src/service/watches.rs
@@ -1,8 +1,9 @@
1use api::pagination::Page;
12use models::common::watch_mode::WatchMode;
23use models::repo::{repo_watch, repos};
34use sea_orm::{
4 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set,
5 TransactionTrait, Unchanged,
5 ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
6 QuerySelect, Set, TransactionTrait, Unchanged,
67};
78use serde::{Deserialize, Serialize};
89use time::OffsetDateTime;
@@ -114,22 +115,22 @@
114115 mode: Option<WatchMode>,
115116 limit: u64,
116117 offset: u64,
117 ) -> Result<Vec<WatcherDto>, GitError> {
118 let mut q = repo_watch::Entity::find()
119 .filter(repo_watch::Column::RepoId.eq(ctx.model.id))
118 ) -> Result<Page<WatcherDto>, GitError> {
119 let mut select =
120 repo_watch::Entity::find().filter(repo_watch::Column::RepoId.eq(ctx.model.id));
121 if let Some(m) = mode {
122 select = select.filter(repo_watch::Column::Mode.eq(m));
123 }
124 let total = select.clone().count(self.db_read()).await?;
125 let rows = select
120126 .order_by_desc(repo_watch::Column::CreatedAt)
121127 .limit(limit)
122 .offset(offset);
123 if let Some(m) = mode {
124 q = q.filter(repo_watch::Column::Mode.eq(m));
125 }
126 let rows = q.all(self.db_read()).await?;
127 if rows.is_empty() {
128 return Ok(Vec::new());
129 }
128 .offset(offset)
129 .all(self.db_read())
130 .await?;
130131 let user_ids: Vec<i64> = rows.iter().map(|r| r.user_id).collect();
131132 let by_id = auth::users::load_public_users(&self.accounts, &user_ids).await?;
132 Ok(rows
133 let items = rows
133134 .into_iter()
134135 .filter_map(|row| {
135136 by_id.get(&row.user_id).map(|u| WatcherDto {
@@ -145,6 +146,7 @@
145146 watching_since: row.created_at,
146147 })
147148 })
148 .collect())
149 .collect();
150 Ok(Page::new(items, total, limit, offset))
149151 }
150152}
Modified lib/service/git/src/share/errors.rs lib/service/git/src/share/errors.rs +1 −0 View lib/service/git/src/share/errors.rs
@@ -24,6 +24,7 @@
2424
2525auth::auth_error_conversion!(GitError);
2626api::access_error_conversion!(GitError, InvalidArgument);
27api::page_error_conversion!(GitError, InvalidArgument);
2728
2829impl From<sea_orm::DbErr> for GitError {
2930 fn from(err: sea_orm::DbErr) -> Self {
Modified lib/service/git/src/errors.rs lib/service/git/src/errors.rs +12 −0 View lib/service/git/src/errors.rs
@@ -73,6 +73,18 @@
7373 }
7474}
7575
76impl From<api::pagination::PageError> for GitApiError {
77 fn from(error: api::pagination::PageError) -> Self {
78 Self::BadRequest(error.to_string())
79 }
80}
81
82impl From<api::pagination::PageError> for ApiError {
83 fn from(error: api::pagination::PageError) -> Self {
84 Self::BadRequest(Box::new(AppResponseData::err(400, error.to_string())))
85 }
86}
87
7688fn is_unique_violation(err: &sea_orm::DbErr) -> bool {
7789 let msg = err.to_string().to_lowercase();
7890 msg.contains("unique") || msg.contains("duplicate") || msg.contains("23505")
Modified lib/service/git/src/openapi.rs lib/service/git/src/openapi.rs +10 −1 View lib/service/git/src/openapi.rs
@@ -111,9 +111,18 @@
111111 ),
112112 components(schemas(
113113 api::pagination::PageQuery,
114 api::pagination::SortOrder,
115 crate::service::repos::RepoSort,
114116 api::pagination::Page<crate::service::repos::RepoSummaryDto>,
115117 api::pagination::Page<crate::service::branches::BranchDto>,
116118 api::pagination::Page<crate::service::tags::TagDto>,
119 api::pagination::Page<crate::service::commits::CommitDto>,
120 api::pagination::Page<crate::service::licenses::LicenseDto>,
121 api::pagination::Page<crate::service::stars::StargazerDto>,
122 api::pagination::Page<crate::service::watches::WatcherDto>,
123 api::pagination::Page<crate::service::topics::TopicDto>,
124 api::pagination::Page<crate::service::topics::TopicRepoDto>,
125 api::pagination::Page<crate::service::refs::BranchInfoDto>,
117126 api::rest::NoData,
118127 crate::service::hooks::HookEventKind,
119128 models::common::watch_mode::WatchMode,
@@ -145,7 +154,7 @@
145154 crate::service::protection::UpdateTagPolicyCmd,
146155 crate::service::commits::CommitStatusDto,
147156 crate::service::commits::CommitStatusDtoState,
148 crate::service::commits::CommitsPageDto,
157 crate::service::commits::CommitDto,
149158 crate::service::commits::SetCommitStatusCmd,
150159 crate::service::conflicts::ConflictDetailDto,
151160 crate::service::conflicts::MergeStatusDto,
Modified lib/service/issues/src/services/boards.rs lib/service/issues/src/services/boards.rs +3 −2 View lib/service/issues/src/services/boards.rs
@@ -59,6 +59,7 @@
5959}
6060
6161#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
62#[serde(deny_unknown_fields)]
6263pub struct ListBoardQuery {
6364 pub dir_id: Option<Uuid>,
6465 pub q: Option<String>,
@@ -274,7 +275,7 @@
274275 ) -> Result<BoardPage, IssueError> {
275276 let space = space_context(self, caller, space_name).await?;
276277
277 let (limit, offset) = self.paginate(q.limit, q.offset);
278 let (limit, offset) = self.paginate(q.limit, q.offset)?;
278279
279280 let dirs_in_space = dirs::Entity::find()
280281 .filter(dirs::Column::SpaceId.eq(Some(space.id())))
@@ -321,7 +322,7 @@
321322
322323 select = select.order_by_desc(issues::Column::CreatedAt);
323324
324 let total = select.clone().count(self.database.read()).await? as i64;
325 let total = select.clone().count(self.database.read()).await?;
325326 let rows = select
326327 .limit(limit)
327328 .offset(offset)
Modified lib/service/issues/src/services/comments.rs lib/service/issues/src/services/comments.rs +40 −7 View lib/service/issues/src/services/comments.rs
@@ -18,6 +18,8 @@
1818use crate::share::authz;
1919use crate::share::lookup::{issue_context, require_issue_access};
2020
21use api::pagination::SortOrder;
22
2123#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
2224pub struct CommentDto {
2325 pub id: Uuid,
@@ -50,12 +52,30 @@
5052 pub content: String,
5153}
5254
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
56#[serde(rename_all = "snake_case")]
57pub enum CommentSort {
58 CreatedAt,
59 UpdatedAt,
60}
61
62impl CommentSort {
63 fn default_order(self) -> SortOrder {
64 match self {
65 Self::CreatedAt => SortOrder::Asc,
66 Self::UpdatedAt => SortOrder::Desc,
67 }
68 }
69}
70
5371#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
72#[serde(deny_unknown_fields)]
5473pub struct ListCommentQuery {
5574 pub parent_id: Option<Uuid>,
5675 pub limit: Option<u64>,
5776 pub offset: Option<u64>,
58 pub sort: Option<String>,
77 pub sort: Option<CommentSort>,
78 pub order: Option<SortOrder>,
5979}
6080
6181#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
@@ -295,7 +315,7 @@
295315 ) -> Result<CommentPage, IssueError> {
296316 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
297317
298 let (limit, offset) = self.paginate(q.limit, q.offset);
318 let (limit, offset) = self.paginate(q.limit, q.offset)?;
299319
300320 let mut select =
301321 comment::Entity::find().filter(comment::Column::IssueId.eq(Some(issue_id)));
@@ -304,13 +324,26 @@
304324 select = select.filter(comment::Column::ParentId.eq(Some(pid)));
305325 }
306326
307 match q.sort.as_deref() {
308 Some("-created_at") => select = select.order_by_desc(comment::Column::CreatedAt),
309 Some("updated_at") => select = select.order_by_desc(comment::Column::UpdatedAt),
310 _ => select = select.order_by_asc(comment::Column::CreatedAt),
327 let sort = q.sort.unwrap_or(CommentSort::CreatedAt);
328 let descending = q.order.unwrap_or(sort.default_order()) == SortOrder::Desc;
329 match sort {
330 CommentSort::UpdatedAt => {
331 if descending {
332 select = select.order_by_desc(comment::Column::UpdatedAt);
333 } else {
334 select = select.order_by_asc(comment::Column::UpdatedAt);
335 }
336 }
337 CommentSort::CreatedAt => {
338 if descending {
339 select = select.order_by_desc(comment::Column::CreatedAt);
340 } else {
341 select = select.order_by_asc(comment::Column::CreatedAt);
342 }
343 }
311344 }
312345
313 let total = select.clone().count(self.database.read()).await? as i64;
346 let total = select.clone().count(self.database.read()).await?;
314347 let rows = select
315348 .limit(limit)
316349 .offset(offset)
Modified lib/service/issues/src/services/content_history.rs lib/service/issues/src/services/content_history.rs +3 −2 View lib/service/issues/src/services/content_history.rs
@@ -25,6 +25,7 @@
2525pub type ContentHistoryPage = api::pagination::Page<ContentHistoryDto>;
2626
2727#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
28#[serde(deny_unknown_fields)]
2829pub struct ListContentHistoryQuery {
2930 pub comment_id: Option<Uuid>,
3031 pub limit: Option<u64>,
@@ -49,7 +50,7 @@
4950 ) -> Result<ContentHistoryPage, IssueError> {
5051 lookup::require_issue_access(self, caller, space_name, board_id, issue_id).await?;
5152
52 let (limit, offset) = self.paginate(q.limit, q.offset);
53 let (limit, offset) = self.paginate(q.limit, q.offset)?;
5354
5455 let mut select = issue_content_history::Entity::find()
5556 .filter(issue_content_history::Column::IssueId.eq(issue_id));
@@ -60,7 +61,7 @@
6061
6162 select = select.order_by_desc(issue_content_history::Column::EditedAt);
6263
63 let total = select.clone().count(self.database.read()).await? as i64;
64 let total = select.clone().count(self.database.read()).await?;
6465 let rows = select
6566 .limit(limit)
6667 .offset(offset)
Modified lib/service/issues/src/services/events.rs lib/service/issues/src/services/events.rs +3 −2 View lib/service/issues/src/services/events.rs
@@ -29,6 +29,7 @@
2929pub type EventPage = api::pagination::Page<EventDto>;
3030
3131#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
32#[serde(deny_unknown_fields)]
3233pub struct ListEventQuery {
3334 pub limit: Option<u64>,
3435 pub offset: Option<u64>,
@@ -52,13 +53,13 @@
5253 ) -> Result<EventPage, IssueError> {
5354 lookup::require_issue_access(self, caller, space_name, board_id, issue_id).await?;
5455
55 let (limit, offset) = self.paginate(q.limit, q.offset);
56 let (limit, offset) = self.paginate(q.limit, q.offset)?;
5657
5758 let select = issue_event_line::Entity::find()
5859 .filter(issue_event_line::Column::IssueId.eq(Some(issue_id)))
5960 .order_by_desc(issue_event_line::Column::CreatedAt);
6061
61 let total = select.clone().count(self.database.read()).await? as i64;
62 let total = select.clone().count(self.database.read()).await?;
6263 let rows = select
6364 .limit(limit)
6465 .offset(offset)
Modified lib/service/issues/src/services/issue_users.rs lib/service/issues/src/services/issue_users.rs +3 −2 View lib/service/issues/src/services/issue_users.rs
@@ -32,6 +32,7 @@
3232}
3333
3434#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
35#[serde(deny_unknown_fields)]
3536pub struct ListIssueUserQuery {
3637 pub limit: Option<u64>,
3738 pub offset: Option<u64>,
@@ -56,13 +57,13 @@
5657 ) -> Result<IssueUserPage, IssueError> {
5758 lookup::require_issue_access(self, caller, space_name, board_id, issue_id).await?;
5859
59 let (limit, offset) = self.paginate(q.limit, q.offset);
60 let (limit, offset) = self.paginate(q.limit, q.offset)?;
6061
6162 let select = issue_user::Entity::find()
6263 .filter(issue_user::Column::IssueId.eq(issue_id))
6364 .order_by_desc(issue_user::Column::CreatedAt);
6465
65 let total = select.clone().count(self.database.read()).await? as i64;
66 let total = select.clone().count(self.database.read()).await?;
6667 let rows = select
6768 .limit(limit)
6869 .offset(offset)
Modified lib/service/issues/src/services/issues.rs lib/service/issues/src/services/issues.rs +65 −10 View lib/service/issues/src/services/issues.rs
@@ -24,6 +24,8 @@
2424use crate::share::authz;
2525use crate::share::lookup::{board_context, issue_context, lookup_issue_by_ref};
2626
27use api::pagination::SortOrder;
28
2729#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
2830#[serde(rename_all = "snake_case")]
2931pub enum IssueState {
@@ -120,7 +122,27 @@
120122 pub label_ids: Option<Vec<Uuid>>,
121123}
122124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
126#[serde(rename_all = "snake_case")]
127pub enum IssueSort {
128 CreatedAt,
129 UpdatedAt,
130 DueAt,
131 Priority,
132 Number,
133}
134
135impl IssueSort {
136 fn default_order(self) -> SortOrder {
137 match self {
138 Self::CreatedAt | Self::UpdatedAt | Self::Priority => SortOrder::Desc,
139 Self::DueAt | Self::Number => SortOrder::Asc,
140 }
141 }
142}
143
123144#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
145#[serde(deny_unknown_fields)]
124146pub struct ListIssueQuery {
125147 pub state: Option<IssueState>,
126148 pub author_id: Option<i64>,
@@ -133,7 +155,8 @@
133155 pub assignee_id: Option<String>,
134156 /// One priority or a comma-separated list (`high,urgent`).
135157 pub priority: Option<String>,
136 pub sort: Option<String>,
158 pub sort: Option<IssueSort>,
159 pub order: Option<SortOrder>,
137160 pub q: Option<String>,
138161 pub limit: Option<u64>,
139162 pub offset: Option<u64>,
@@ -675,7 +698,7 @@
675698 ) -> Result<IssuePage, IssueError> {
676699 let (_, board) = board_context(self, caller, space_name, board_id).await?;
677700
678 let (limit, offset) = self.paginate(q.limit, q.offset);
701 let (limit, offset) = self.paginate(q.limit, q.offset)?;
679702
680703 let mut select =
681704 issue_item::Entity::find().filter(issue_item::Column::CollectionId.eq(board.id));
@@ -746,15 +769,47 @@
746769 select = select.filter(issue_item::Column::Priority.is_in(priorities));
747770 }
748771
749 match q.sort.as_deref() {
750 Some("updated_at") => select = select.order_by_desc(issue_item::Column::UpdatedAt),
751 Some("due_at") => select = select.order_by_asc(issue_item::Column::DueAt),
752 Some("priority") => select = select.order_by_desc(issue_item::Column::Priority),
753 Some("number") => select = select.order_by_asc(issue_item::Column::AutoincrementId),
754 _ => select = select.order_by_desc(issue_item::Column::CreatedAt),
755 }
772 let sort = q.sort.unwrap_or(IssueSort::CreatedAt);
773 let descending = q.order.unwrap_or(sort.default_order()) == SortOrder::Desc;
774 select = match sort {
775 IssueSort::UpdatedAt => {
776 if descending {
777 select.order_by_desc(issue_item::Column::UpdatedAt)
778 } else {
779 select.order_by_asc(issue_item::Column::UpdatedAt)
780 }
781 }
782 IssueSort::DueAt => {
783 if descending {
784 select.order_by_desc(issue_item::Column::DueAt)
785 } else {
786 select.order_by_asc(issue_item::Column::DueAt)
787 }
788 }
789 IssueSort::Priority => {
790 if descending {
791 select.order_by_desc(issue_item::Column::Priority)
792 } else {
793 select.order_by_asc(issue_item::Column::Priority)
794 }
795 }
796 IssueSort::Number => {
797 if descending {
798 select.order_by_desc(issue_item::Column::AutoincrementId)
799 } else {
800 select.order_by_asc(issue_item::Column::AutoincrementId)
801 }
802 }
803 IssueSort::CreatedAt => {
804 if descending {
805 select.order_by_desc(issue_item::Column::CreatedAt)
806 } else {
807 select.order_by_asc(issue_item::Column::CreatedAt)
808 }
809 }
810 };
756811
757 let total = select.clone().count(self.database.read()).await? as i64;
812 let total = select.clone().count(self.database.read()).await?;
758813 let rows = select
759814 .limit(limit)
760815 .offset(offset)
Modified lib/service/issues/src/services/labels.rs lib/service/issues/src/services/labels.rs +3 −2 View lib/service/issues/src/services/labels.rs
@@ -78,6 +78,7 @@
7878}
7979
8080#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
81#[serde(deny_unknown_fields)]
8182pub struct ListLabelQuery {
8283 pub q: Option<String>,
8384 pub is_archived: Option<bool>,
@@ -190,7 +191,7 @@
190191 ) -> Result<LabelPage, IssueError> {
191192 let (_, board) = board_context(self, caller, space_name, board_id).await?;
192193
193 let (limit, offset) = self.paginate(q.limit, q.offset);
194 let (limit, offset) = self.paginate(q.limit, q.offset)?;
194195
195196 let mut select =
196197 issue_label::Entity::find().filter(issue_label::Column::DirId.eq(board.dir_id));
@@ -208,7 +209,7 @@
208209
209210 select = select.order_by_desc(issue_label::Column::CreatedAt);
210211
211 let total = select.clone().count(self.database.read()).await? as i64;
212 let total = select.clone().count(self.database.read()).await?;
212213 let rows = select
213214 .limit(limit)
214215 .offset(offset)
Modified lib/service/issues/src/services/members.rs lib/service/issues/src/services/members.rs +3 −2 View lib/service/issues/src/services/members.rs
@@ -36,6 +36,7 @@
3636}
3737
3838#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
39#[serde(deny_unknown_fields)]
3940pub struct ListIssueMemberQuery {
4041 pub kind: Option<IssueMemberKind>,
4142 pub limit: Option<u64>,
@@ -208,7 +209,7 @@
208209 ) -> Result<IssueMemberPage, IssueError> {
209210 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
210211
211 let (limit, offset) = self.paginate(q.limit, q.offset);
212 let (limit, offset) = self.paginate(q.limit, q.offset)?;
212213
213214 let mut select =
214215 issue_member::Entity::find().filter(issue_member::Column::IssueId.eq(issue_id));
@@ -219,7 +220,7 @@
219220
220221 select = select.order_by_asc(issue_member::Column::CreatedAt);
221222
222 let total = select.clone().count(self.database.read()).await? as i64;
223 let total = select.clone().count(self.database.read()).await?;
223224 let rows = select
224225 .limit(limit)
225226 .offset(offset)
Modified lib/service/issues/src/services/milestones.rs lib/service/issues/src/services/milestones.rs +3 −2 View lib/service/issues/src/services/milestones.rs
@@ -60,6 +60,7 @@
6060}
6161
6262#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
63#[serde(deny_unknown_fields)]
6364pub struct ListMilestoneQuery {
6465 pub is_closed: Option<bool>,
6566 pub q: Option<String>,
@@ -213,7 +214,7 @@
213214 ) -> Result<MilestonePage, IssueError> {
214215 let (_, board) = board_context(self, caller, space_name, board_id).await?;
215216
216 let (limit, offset) = self.paginate(q.limit, q.offset);
217 let (limit, offset) = self.paginate(q.limit, q.offset)?;
217218
218219 let mut select =
219220 milestone::Entity::find().filter(milestone::Column::CollectionId.eq(board.id));
@@ -231,7 +232,7 @@
231232
232233 select = select.order_by_desc(milestone::Column::CreatedAt);
233234
234 let total = select.clone().count(self.database.read()).await? as i64;
235 let total = select.clone().count(self.database.read()).await?;
235236 let rows = select
236237 .limit(limit)
237238 .offset(offset)
Modified lib/service/issues/src/services/service.rs lib/service/issues/src/services/service.rs +5 −1 View lib/service/issues/src/services/service.rs
@@ -49,7 +49,11 @@
4949 }
5050 }
5151
52 pub fn paginate(&self, limit: Option<u64>, offset: Option<u64>) -> (u64, u64) {
52 pub fn paginate(
53 &self,
54 limit: Option<u64>,
55 offset: Option<u64>,
56 ) -> Result<(u64, u64), api::pagination::PageError> {
5357 api::pagination::page_bounds(
5458 limit,
5559 offset,
Modified lib/service/issues/src/services/tracked_time.rs lib/service/issues/src/services/tracked_time.rs +3 −2 View lib/service/issues/src/services/tracked_time.rs
@@ -25,6 +25,7 @@
2525pub type TrackedTimePage = api::pagination::Page<TrackedTimeDto>;
2626
2727#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
28#[serde(deny_unknown_fields)]
2829pub struct ListTrackedTimeQuery {
2930 pub user_id: Option<i64>,
3031 pub limit: Option<u64>,
@@ -162,7 +163,7 @@
162163 ) -> Result<TrackedTimePage, IssueError> {
163164 lookup::require_issue_access(self, caller, space_name, board_id, issue_id).await?;
164165
165 let (limit, offset) = self.paginate(q.limit, q.offset);
166 let (limit, offset) = self.paginate(q.limit, q.offset)?;
166167
167168 let mut select =
168169 tracked_time::Entity::find().filter(tracked_time::Column::IssueId.eq(issue_id));
@@ -173,7 +174,7 @@
173174
174175 select = select.order_by_desc(tracked_time::Column::CreatedAt);
175176
176 let total = select.clone().count(self.database.read()).await? as i64;
177 let total = select.clone().count(self.database.read()).await?;
177178 let rows = select
178179 .limit(limit)
179180 .offset(offset)
Modified lib/service/issues/src/errors.rs lib/service/issues/src/errors.rs +1 −0 View lib/service/issues/src/errors.rs
@@ -41,6 +41,7 @@
4141}
4242
4343api::json_payload_conversion!(IssueError);
44api::page_error_conversion!(IssueError, BadRequest);
4445api::access_error_conversion!(IssueError, BadRequest);
4546
4647api::api_error! {
Modified lib/service/pulls/src/services/comments.rs lib/service/pulls/src/services/comments.rs +2 −2 View lib/service/pulls/src/services/comments.rs
@@ -182,11 +182,11 @@
182182 pull_id: Uuid,
183183 page: PageQuery,
184184 ) -> Result<CommentPage, PullError> {
185 let (limit, offset) = page.page();
185 let (limit, offset) = page.bounds()?;
186186 let total = pull_request_comment::Entity::find()
187187 .filter(pull_request_comment::Column::PullRequestId.eq(pull_id))
188188 .count(self.db_read())
189 .await? as i64;
189 .await?;
190190 let rows = pull_request_comment::Entity::find()
191191 .filter(pull_request_comment::Column::PullRequestId.eq(pull_id))
192192 .order_by_asc(pull_request_comment::Column::CreatedAt)
Modified lib/service/pulls/src/services/events.rs lib/service/pulls/src/services/events.rs +2 −2 View lib/service/pulls/src/services/events.rs
@@ -178,11 +178,11 @@
178178 pull_id: Uuid,
179179 page: PageQuery,
180180 ) -> Result<EventPage, PullError> {
181 let (limit, offset) = page.page();
181 let (limit, offset) = page.bounds()?;
182182 let total = pull_request_event_line::Entity::find()
183183 .filter(pull_request_event_line::Column::PullRequestId.eq(pull_id))
184184 .count(self.db_read())
185 .await? as i64;
185 .await?;
186186 let rows = pull_request_event_line::Entity::find()
187187 .filter(pull_request_event_line::Column::PullRequestId.eq(pull_id))
188188 .order_by_asc(pull_request_event_line::Column::CreatedAt)
Modified lib/service/pulls/src/services/inbox.rs lib/service/pulls/src/services/inbox.rs +3 −3 View lib/service/pulls/src/services/inbox.rs
@@ -20,6 +20,7 @@
2020}
2121
2222#[derive(Debug, Clone, Default, Deserialize, IntoParams)]
23#[serde(deny_unknown_fields)]
2324pub struct InboxQuery {
2425 /// open | closed | merged | all
2526 #[serde(default)]
@@ -39,8 +40,7 @@
3940 scope: InboxScope,
4041 query: InboxQuery,
4142 ) -> Result<InboxPage, PullError> {
42 let limit = query.limit.unwrap_or(50).clamp(1, 100);
43 let offset = query.offset.unwrap_or(0);
43 let (limit, offset) = api::pagination::page_bounds(query.limit, query.offset, 50, 100)?;
4444 let base: Select<pull_requests::Entity> = match scope {
4545 InboxScope::Created => {
4646 pull_requests::Entity::find().filter(pull_requests::Column::AuthorId.eq(caller))
@@ -79,7 +79,7 @@
7979 return Err(PullError::BadRequest(format!("unknown state `{other}`")));
8080 }
8181 };
82 let total = select.clone().count(self.db_read()).await? as i64;
82 let total = select.clone().count(self.db_read()).await?;
8383 select = select.order_by_desc(pull_requests::Column::UpdatedAt);
8484 let rows = select
8585 .limit(limit)
Modified lib/service/pulls/src/services/pulls.rs lib/service/pulls/src/services/pulls.rs +26 −13 View lib/service/pulls/src/services/pulls.rs
@@ -1,4 +1,4 @@
1use api::pagination::Page;
1use api::pagination::{Page, SortOrder};
22use api::users::UserDto;
33use git::facade::GitContext;
44use models::issue::issue_relation_pull;
@@ -82,7 +82,22 @@
8282
8383pub type PullPage = Page<PullDto>;
8484
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
86#[serde(rename_all = "snake_case")]
87pub enum PullSort {
88 Number,
89 Created,
90 Updated,
91}
92
93impl PullSort {
94 fn default_order(self) -> SortOrder {
95 SortOrder::Desc
96 }
97}
98
8599#[derive(Debug, Clone, Default, Deserialize, IntoParams)]
100#[serde(deny_unknown_fields)]
86101pub struct ListPullQuery {
87102 /// open | closed | merged | all
88103 #[serde(default)]
@@ -97,12 +112,10 @@
97112 pub draft: Option<bool>,
98113 #[serde(default)]
99114 pub search: Option<String>,
100 /// number | created | updated
101115 #[serde(default)]
102 pub sort: Option<String>,
103 /// asc | desc
116 pub sort: Option<PullSort>,
104117 #[serde(default)]
105 pub order: Option<String>,
118 pub order: Option<SortOrder>,
106119 #[serde(default)]
107120 pub limit: Option<u64>,
108121 #[serde(default)]
@@ -196,8 +209,7 @@
196209 }
197210
198211 pub async fn list_pulls(&self, repo_id: Uuid, q: ListPullQuery) -> Result<PullPage, PullError> {
199 let limit = q.limit.unwrap_or(50).clamp(1, 100);
200 let offset = q.offset.unwrap_or(0);
212 let (limit, offset) = api::pagination::page_bounds(q.limit, q.offset, 50, 100)?;
201213 let state = q.state.clone().unwrap_or_else(|| "open".to_string());
202214 let mut select =
203215 pull_requests::Entity::find().filter(pull_requests::Column::RepoId.eq(repo_id));
@@ -236,24 +248,25 @@
236248 select = select.filter(pull_requests::Column::Title.contains(search));
237249 }
238250
239 let total = select.clone().count(self.db_read()).await? as i64;
240 let descending = q.order.as_deref().unwrap_or("desc") != "asc";
241 select = match q.sort.as_deref().unwrap_or("created") {
242 "number" => {
251 let total = select.clone().count(self.db_read()).await?;
252 let sort = q.sort.unwrap_or(PullSort::Created);
253 let descending = q.order.unwrap_or(sort.default_order()) == SortOrder::Desc;
254 select = match sort {
255 PullSort::Number => {
243256 if descending {
244257 select.order_by_desc(pull_requests::Column::AutoincrementId)
245258 } else {
246259 select.order_by_asc(pull_requests::Column::AutoincrementId)
247260 }
248261 }
249 "updated" => {
262 PullSort::Updated => {
250263 if descending {
251264 select.order_by_desc(pull_requests::Column::UpdatedAt)
252265 } else {
253266 select.order_by_asc(pull_requests::Column::UpdatedAt)
254267 }
255268 }
256 _ => {
269 PullSort::Created => {
257270 if descending {
258271 select.order_by_desc(pull_requests::Column::CreatedAt)
259272 } else {
Modified lib/service/pulls/src/errors.rs lib/service/pulls/src/errors.rs +1 −0 View lib/service/pulls/src/errors.rs
@@ -30,6 +30,7 @@
3030auth::auth_error_conversion!(PullError);
3131api::db_error_conversions!(PullError);
3232api::json_payload_conversion!(PullError);
33api::page_error_conversion!(PullError, BadRequest);
3334api::domain_error_passthrough!(PullError from issues::errors::IssueError);
3435
3536impl From<git::facade::GitError> for PullError {
Modified lib/service/pulls/src/openapi.rs lib/service/pulls/src/openapi.rs +2 −0 View lib/service/pulls/src/openapi.rs
@@ -53,6 +53,8 @@
5353 ),
5454 components(schemas(
5555 AppResponseData<NoData>,
56 api::pagination::SortOrder,
57 crate::services::pulls::PullSort,
5658 AppResponseData<crate::services::pulls::PullDto>,
5759 AppResponseData<crate::services::pulls::PullPage>,
5860 AppResponseData<crate::services::comments::CommentPage>,
Modified lib/service/space/src/handlers/space_admin.rs lib/service/space/src/handlers/space_admin.rs +52 −35 View lib/service/space/src/handlers/space_admin.rs
@@ -37,38 +37,46 @@
3737 pub invite_id: Uuid,
3838}
3939
40#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
40#[derive(Debug, serde::Deserialize, utoipa::ToSchema, utoipa::IntoParams)]
41#[serde(deny_unknown_fields)]
4142pub struct OrgListQuery {
42 pub page: Option<u64>,
43 pub page_size: Option<u64>,
43 pub limit: Option<u64>,
44 pub offset: Option<u64>,
4445 pub include_archived: Option<bool>,
4546}
4647
4748impl OrgListQuery {
48 fn limit(&self) -> u64 {
49 self.page_size.unwrap_or(20).clamp(1, 100)
50 }
51
52 fn offset(&self) -> u64 {
53 self.page.unwrap_or(1).saturating_sub(1) * self.limit()
49 fn bounds(&self) -> Result<(u64, u64), api::pagination::PageError> {
50 api::pagination::page_bounds(self.limit, self.offset, 20, 100)
5451 }
5552}
5653
5754#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
55#[serde(deny_unknown_fields)]
5856pub struct MemberListQuery {
59 pub page: Option<u64>,
60 pub page_size: Option<u64>,
57 pub limit: Option<u64>,
58 pub offset: Option<u64>,
6159 pub include_inactive: Option<bool>,
6260 pub include_banned: Option<bool>,
6361}
6462
6563impl MemberListQuery {
66 fn limit(&self) -> u64 {
67 self.page_size.unwrap_or(20).clamp(1, 100)
64 fn bounds(&self) -> Result<(u64, u64), api::pagination::PageError> {
65 api::pagination::page_bounds(self.limit, self.offset, 20, 100)
6866 }
67}
6968
70 fn offset(&self) -> u64 {
71 self.page.unwrap_or(1).saturating_sub(1) * self.limit()
69#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
70#[serde(deny_unknown_fields)]
71pub struct InviteListQuery {
72 pub limit: Option<u64>,
73 pub offset: Option<u64>,
74 pub include_revoked: Option<bool>,
75}
76
77impl InviteListQuery {
78 fn bounds(&self) -> Result<(u64, u64), api::pagination::PageError> {
79 api::pagination::page_bounds(self.limit, self.offset, 20, 100)
7280 }
7381}
7482
@@ -164,8 +172,9 @@
164172 operation_id = "space_list",
165173 summary = "列示我加入的组织",
166174 description = "默认只返回 ACTIVE 组织;`include_archived=true` 时一并返回已归档组织。",
175 params(OrgListQuery),
167176 responses(
168 (status = 200, description = "组织列表", body = AppResponseData<Vec<OrganizationSummary>>),
177 (status = 200, description = "组织列表", body = AppResponseData<api::pagination::Page<OrganizationSummary>>),
169178 (status = 401, description = "会话未绑定账号", body = AppResponseData<NoData>)
170179 )
171180)]
@@ -174,18 +183,19 @@
174183 session: Session,
175184 request_id: RequestId,
176185 Query(query): Query<OrgListQuery>,
177) -> Result<AppResponseData<Vec<OrganizationSummary>>, ApiError> {
186) -> Result<AppResponseData<api::pagination::Page<OrganizationSummary>>, ApiError> {
178187 let user_id = svc
179188 .policy
180189 .require_session(&session, Some(request_id.0.as_str()))
181190 .await?;
191 let (limit, offset) = query.bounds()?;
182192 let rows = svc
183193 .list_spaces(
184194 user_id,
185195 0,
186196 query.include_archived.unwrap_or(false),
187 query.limit(),
188 query.offset(),
197 limit,
198 offset,
189199 Some(request_id.0.as_str()),
190200 )
191201 .await?;
@@ -415,11 +425,13 @@
415425 description = "需要 `member.view`。默认只返回活跃成员。",
416426 params(
417427 ("space_name" = String, Path, description = "空间 slug"),
428 ("limit" = Option<u64>, Query, description = "每页条数,默认 20,最多 100"),
429 ("offset" = Option<u64>, Query, description = "跳过条数,最多 5000"),
418430 ("include_inactive" = Option<bool>, Query, description = "包含已退出成员"),
419431 ("include_banned" = Option<bool>, Query, description = "包含已封禁成员")
420432 ),
421433 responses(
422 (status = 200, description = "成员列表", body = AppResponseData<Vec<MemberSummary>>),
434 (status = 200, description = "成员列表", body = AppResponseData<api::pagination::Page<MemberSummary>>),
423435 (status = 403, description = "权限不足", body = AppResponseData<NoData>)
424436 )
425437)]
@@ -429,20 +441,21 @@
429441 request_id: RequestId,
430442 Path(path): Path<SpacePath>,
431443 Query(query): Query<MemberListQuery>,
432) -> Result<AppResponseData<Vec<MemberSummary>>, ApiError> {
444) -> Result<AppResponseData<api::pagination::Page<MemberSummary>>, ApiError> {
433445 let user_id = svc
434446 .policy
435447 .require_session(&session, Some(request_id.0.as_str()))
436448 .await?;
437449 let space_id = svc.space_id_by_name(user_id, &path.space_name).await?;
450 let (limit, offset) = query.bounds()?;
438451 let rows = svc
439452 .admin_list_members(
440453 user_id,
441454 space_id,
442455 query.include_inactive.unwrap_or(false),
443456 query.include_banned.unwrap_or(false),
444 query.limit(),
445 query.offset(),
457 limit,
458 offset,
446459 Some(request_id.0.as_str()),
447460 )
448461 .await?;
@@ -807,11 +820,11 @@
807820 description = "需要 `member.ban`。按封禁时间倒序。",
808821 params(
809822 ("space_name" = String, Path, description = "空间 slug"),
810 ("page" = Option<u64>, Query, description = "页码,从 1 开始"),
811 ("page_size" = Option<u64>, Query, description = "每页条数")
823 ("limit" = Option<u64>, Query, description = "每页条数,默认 20,最多 100"),
824 ("offset" = Option<u64>, Query, description = "跳过条数,最多 5000")
812825 ),
813826 responses(
814 (status = 200, description = "封禁列表", body = AppResponseData<Vec<MemberSummary>>),
827 (status = 200, description = "封禁列表", body = AppResponseData<api::pagination::Page<MemberSummary>>),
815828 (status = 403, description = "权限不足", body = AppResponseData<NoData>)
816829 )
817830)]
@@ -821,18 +834,19 @@
821834 request_id: RequestId,
822835 Path(path): Path<SpacePath>,
823836 Query(query): Query<MemberListQuery>,
824) -> Result<AppResponseData<Vec<MemberSummary>>, ApiError> {
837) -> Result<AppResponseData<api::pagination::Page<MemberSummary>>, ApiError> {
825838 let user_id = svc
826839 .policy
827840 .require_session(&session, Some(request_id.0.as_str()))
828841 .await?;
829842 let space_id = svc.space_id_by_name(user_id, &path.space_name).await?;
843 let (limit, offset) = query.bounds()?;
830844 let rows = svc
831845 .admin_list_bans(
832846 user_id,
833847 space_id,
834 query.limit(),
835 query.offset(),
848 limit,
849 offset,
836850 Some(request_id.0.as_str()),
837851 )
838852 .await?;
@@ -1449,10 +1463,12 @@
14491463 description = "需要 `invite.view`。不含 token 明文。",
14501464 params(
14511465 ("space_name" = String, Path, description = "空间 slug"),
1466 ("limit" = Option<u64>, Query, description = "每页条数,默认 20,最多 100"),
1467 ("offset" = Option<u64>, Query, description = "跳过条数,最多 5000"),
14521468 ("include_revoked" = Option<bool>, Query, description = "包含已撤销邀请")
14531469 ),
14541470 responses(
1455 (status = 200, description = "邀请列表", body = AppResponseData<Vec<InviteSummary>>),
1471 (status = 200, description = "邀请列表", body = AppResponseData<api::pagination::Page<InviteSummary>>),
14561472 (status = 403, description = "权限不足", body = AppResponseData<NoData>)
14571473 )
14581474)]
@@ -1461,20 +1477,21 @@
14611477 session: Session,
14621478 request_id: RequestId,
14631479 Path(path): Path<SpacePath>,
1464 Query(query): Query<OrgListQuery>,
1465) -> Result<AppResponseData<Vec<InviteSummary>>, ApiError> {
1480 Query(query): Query<InviteListQuery>,
1481) -> Result<AppResponseData<api::pagination::Page<InviteSummary>>, ApiError> {
14661482 let user_id = svc
14671483 .policy
14681484 .require_session(&session, Some(request_id.0.as_str()))
14691485 .await?;
14701486 let space_id = svc.space_id_by_name(user_id, &path.space_name).await?;
1487 let (limit, offset) = query.bounds()?;
14711488 let rows = svc
14721489 .admin_list_invites(
14731490 user_id,
14741491 space_id,
1475 query.include_archived.unwrap_or(false),
1476 query.limit(),
1477 query.offset(),
1492 query.include_revoked.unwrap_or(false),
1493 limit,
1494 offset,
14781495 Some(request_id.0.as_str()),
14791496 )
14801497 .await?;
Modified lib/service/space/src/services/groups.rs lib/service/space/src/services/groups.rs +2 −2 View lib/service/space/src/services/groups.rs
@@ -516,13 +516,13 @@
516516 q.offset,
517517 self.policy.settings.space.default_pagination,
518518 self.policy.settings.space.max_pagination,
519 );
519 )?;
520520
521521 let select = space_groups::Entity::find()
522522 .filter(space_groups::Column::SpaceId.eq(space.id))
523523 .order_by_asc(space_groups::Column::Position);
524524
525 let total = select.clone().count(self.access.database.read()).await? as i64;
525 let total = select.clone().count(self.access.database.read()).await?;
526526 let rows = select
527527 .limit(limit)
528528 .offset(offset)
Modified lib/service/space/src/services/invite.rs lib/service/space/src/services/invite.rs +6 −4 View lib/service/space/src/services/invite.rs
@@ -2,8 +2,8 @@
22use sea_orm::entity::Set;
33use sea_orm::sea_query::{Expr, ExprTrait};
44use sea_orm::{
5 ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QueryOrder,
6 QuerySelect, TransactionTrait,
5 ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, PaginatorTrait, QueryFilter,
6 QueryOrder, QuerySelect, TransactionTrait,
77};
88use uuid::Uuid;
99
@@ -188,7 +188,7 @@
188188 limit: u64,
189189 offset: u64,
190190 request_id: Option<&str>,
191 ) -> SpaceResult<Vec<InviteSummary>> {
191 ) -> SpaceResult<api::pagination::Page<InviteSummary>> {
192192 self.require_permission(actor_user_id, organization_id, "invite.view", request_id)
193193 .await?;
194194 let mut query = space_invites::Entity::find()
@@ -196,13 +196,15 @@
196196 if !include_revoked {
197197 query = query.filter(space_invites::Column::RevokedAt.is_null());
198198 }
199 let total = query.clone().count(self.access.database.read()).await?;
199200 let rows = query
200201 .order_by_desc(space_invites::Column::CreatedAt)
201202 .limit(limit)
202203 .offset(offset)
203204 .all(self.access.database.read())
204205 .await?;
205 Ok(rows.iter().map(invite_summary).collect())
206 let items = rows.iter().map(invite_summary).collect();
207 Ok(api::pagination::Page::new(items, total, limit, offset))
206208 }
207209
208210 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, invite_id = %invite_id, request_id = %envelope::context::resolve_request_id(request_id)))]
Modified lib/service/space/src/services/join_requests.rs lib/service/space/src/services/join_requests.rs +41 −12 View lib/service/space/src/services/join_requests.rs
@@ -12,6 +12,8 @@
1212use utoipa::{IntoParams, ToSchema};
1313use uuid::Uuid;
1414
15use api::pagination::SortOrder;
16
1517use crate::authority;
1618use crate::errors::SpaceError;
1719use crate::services::dto::{UserDto, resolve_space_image_url};
@@ -57,10 +59,25 @@
5759 pub offset: Option<u64>,
5860}
5961
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
63#[serde(rename_all = "snake_case")]
64pub enum ExploreSort {
65 Members,
66 Newest,
67}
68
69impl ExploreSort {
70 fn default_order(self) -> SortOrder {
71 SortOrder::Desc
72 }
73}
74
6075#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
76#[serde(deny_unknown_fields)]
6177pub struct ExploreQuery {
6278 pub q: Option<String>,
63 pub sort: Option<String>,
79 pub sort: Option<ExploreSort>,
80 pub order: Option<SortOrder>,
6481 pub limit: Option<u64>,
6582 pub offset: Option<u64>,
6683}
@@ -179,7 +196,7 @@
179196 query.offset,
180197 self.policy.settings.space.default_pagination,
181198 self.policy.settings.space.max_pagination,
182 );
199 )?;
183200
184201 let rows =
185202 crate::authority::list_public_spaces(self, query.q.as_deref().unwrap_or_default())
@@ -189,15 +206,27 @@
189206 let count = crate::authority::member_count(self, row.id).await?;
190207 ranked.push((row, count));
191208 }
192 if query.sort.as_deref() == Some("newest") {
193 ranked.sort_by(|a, b| b.0.created_at.cmp(&a.0.created_at));
194 } else {
195 ranked.sort_by(|a, b| {
196 b.1.cmp(&a.1)
197 .then_with(|| b.0.created_at.cmp(&a.0.created_at))
198 });
209 let sort = query.sort.unwrap_or(ExploreSort::Members);
210 let descending = query.order.unwrap_or(sort.default_order()) == SortOrder::Desc;
211 match sort {
212 ExploreSort::Newest => ranked.sort_by(|a, b| {
213 if descending {
214 b.0.created_at.cmp(&a.0.created_at)
215 } else {
216 a.0.created_at.cmp(&b.0.created_at)
217 }
218 }),
219 ExploreSort::Members => ranked.sort_by(|a, b| {
220 if descending {
221 b.1.cmp(&a.1)
222 .then_with(|| b.0.created_at.cmp(&a.0.created_at))
223 } else {
224 a.1.cmp(&b.1)
225 .then_with(|| b.0.created_at.cmp(&a.0.created_at))
226 }
227 }),
199228 }
200 let total = ranked.len() as i64;
229 let total = ranked.len() as u64;
201230 let rows: Vec<(spaces::Model, i64)> = ranked
202231 .into_iter()
203232 .skip(offset as usize)
@@ -437,7 +466,7 @@
437466 query.offset,
438467 self.policy.settings.space.default_pagination,
439468 self.policy.settings.space.max_pagination,
440 );
469 )?;
441470
442471 let (rows, total) = self
443472 .admin_list_join_requests(caller, space.id, query.status, limit, offset, None)
@@ -452,7 +481,7 @@
452481 .into_iter()
453482 .map(|row| join_request_dto(row, &users))
454483 .collect(),
455 total: total as i64,
484 total: total,
456485 limit,
457486 offset,
458487 })
Modified lib/service/space/src/services/member.rs lib/service/space/src/services/member.rs +13 −8 View lib/service/space/src/services/member.rs
@@ -2,8 +2,8 @@
22use sea_orm::entity::Set;
33use sea_orm::sea_query::{Expr, ExprTrait};
44use sea_orm::{
5 ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QueryOrder,
6 QuerySelect, TransactionTrait,
5 ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, PaginatorTrait, QueryFilter,
6 QueryOrder, QuerySelect, TransactionTrait,
77};
88use uuid::Uuid;
99
@@ -135,7 +135,7 @@
135135 limit: u64,
136136 offset: u64,
137137 request_id: Option<&str>,
138 ) -> SpaceResult<Vec<MemberSummary>> {
138 ) -> SpaceResult<api::pagination::Page<MemberSummary>> {
139139 self.require_permission(actor_user_id, organization_id, "member.view", request_id)
140140 .await?;
141141 let mut query = space_members::Entity::find()
@@ -146,13 +146,15 @@
146146 if !include_banned {
147147 query = query.filter(space_members::Column::BannedAt.is_null());
148148 }
149 let total = query.clone().count(self.access.database.read()).await?;
149150 let rows = query
150151 .order_by_asc(space_members::Column::JoinedAt)
151152 .limit(limit)
152153 .offset(offset)
153154 .all(self.access.database.read())
154155 .await?;
155 self.member_summaries(&rows).await
156 let items = self.member_summaries(&rows).await?;
157 Ok(api::pagination::Page::new(items, total, limit, offset))
156158 }
157159
158160 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %envelope::context::resolve_request_id(request_id)))]
@@ -771,18 +773,21 @@
771773 limit: u64,
772774 offset: u64,
773775 request_id: Option<&str>,
774 ) -> SpaceResult<Vec<MemberSummary>> {
776 ) -> SpaceResult<api::pagination::Page<MemberSummary>> {
775777 self.require_permission(actor_user_id, organization_id, "member.ban", request_id)
776778 .await?;
777 let rows = space_members::Entity::find()
779 let query = space_members::Entity::find()
778780 .filter(space_members::Column::OrganizationId.eq(organization_id))
779 .filter(space_members::Column::BannedAt.is_not_null())
781 .filter(space_members::Column::BannedAt.is_not_null());
782 let total = query.clone().count(self.access.database.read()).await?;
783 let rows = query
780784 .order_by_desc(space_members::Column::BannedAt)
781785 .limit(limit)
782786 .offset(offset)
783787 .all(self.access.database.read())
784788 .await?;
785 self.member_summaries(&rows).await
789 let items = self.member_summaries(&rows).await?;
790 Ok(api::pagination::Page::new(items, total, limit, offset))
786791 }
787792
788793 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, user_id = user_id, request_id = %envelope::context::resolve_request_id(request_id)))]
Modified lib/service/space/src/services/service.rs lib/service/space/src/services/service.rs +4 −3 View lib/service/space/src/services/service.rs
@@ -374,7 +374,7 @@
374374 limit: u64,
375375 offset: u64,
376376 request_id: Option<&str>,
377 ) -> SpaceResult<Vec<OrganizationSummary>> {
377 ) -> SpaceResult<api::pagination::Page<OrganizationSummary>> {
378378 let target = if user_id == 0 { actor_user_id } else { user_id };
379379 if target != actor_user_id && !self.is_platform_admin(actor_user_id).await? {
380380 return Err(SpaceError::Forbidden);
@@ -390,7 +390,7 @@
390390 .all(self.access.database.read())
391391 .await?;
392392 if ids.is_empty() {
393 return Ok(Vec::new());
393 return Ok(api::pagination::Page::new(Vec::new(), 0, limit, offset));
394394 }
395395
396396 let mut query = spaces::Entity::find()
@@ -399,6 +399,7 @@
399399 if !include_archived {
400400 query = query.filter(spaces::Column::Status.ne(SpaceStatus::Archived));
401401 }
402 let total = query.clone().count(self.access.database.read()).await?;
402403 let rows = query
403404 .order_by_desc(spaces::Column::UpdatedAt)
404405 .limit(limit)
@@ -410,7 +411,7 @@
410411 for row in rows {
411412 out.push(self.organization_summary(&row, Some(target)).await?);
412413 }
413 Ok(out)
414 Ok(api::pagination::Page::new(out, total, limit, offset))
414415 }
415416
416417 /// 公开目录:ACTIVE 且非 PRIVATE 的组织。不要求成员身份。
Modified lib/service/space/src/errors.rs lib/service/space/src/errors.rs +1 −0 View lib/service/space/src/errors.rs
@@ -137,6 +137,7 @@
137137api::db_error_conversions!(SpaceError);
138138storage::storage_error_conversion!(SpaceError);
139139api::json_payload_conversion!(SpaceError);
140api::page_error_conversion!(SpaceError, BadRequest);
140141
141142api::api_error! {
142143 log: "space",
Modified lib/service/space/src/openapi.rs lib/service/space/src/openapi.rs +6 −0 View lib/service/space/src/openapi.rs
@@ -79,6 +79,8 @@
7979 ),
8080 components(
8181 schemas(
82 api::pagination::SortOrder,
83 crate::services::join_requests::ExploreSort,
8284 AppResponseData<crate::services::dto::SpaceDto>,
8385 AppResponseData<crate::services::join_requests::ExploreSpacePage>,
8486 AppResponseData<crate::services::join_requests::JoinRequestDto>,
@@ -133,6 +135,9 @@
133135 crate::services::types::MemberSummary,
134136 crate::services::types::RoleSummary,
135137 crate::services::types::InviteSummary,
138 api::pagination::Page<crate::services::types::OrganizationSummary>,
139 api::pagination::Page<crate::services::types::MemberSummary>,
140 api::pagination::Page<crate::services::types::InviteSummary>,
136141 crate::services::types::PermissionGrant,
137142 crate::services::types::PermissionCheck,
138143 crate::services::types::CreateOrganizationBody,
@@ -148,6 +153,7 @@
148153 crate::handlers::space_admin::SpaceInvitePath,
149154 crate::handlers::space_admin::OrgListQuery,
150155 crate::handlers::space_admin::MemberListQuery,
156 crate::handlers::space_admin::InviteListQuery,
151157 crate::handlers::space_admin::UpdateRoleBody,
152158 crate::handlers::space_admin::TransferBody,
153159 crate::handlers::space_admin::ReasonBody,
Modified lib/service/user/src/handlers/list_push_subscriptions.rs lib/service/user/src/handlers/list_push_subscriptions.rs +3 −2 View lib/service/user/src/handlers/list_push_subscriptions.rs
@@ -1,5 +1,6 @@
11use ::session::Session;
22use api::http::{Query, State};
3use api::pagination::Page;
34use api::rest::{AppResponseData, NoData};
45
56use crate::errors::ApiError;
@@ -15,7 +16,7 @@
1516 description = "分页列出当前用户的浏览器推送订阅,不返回密钥。",
1617 params(PageQuery),
1718 responses(
18 (status = 200, description = "list push subscriptions", body = AppResponseData<Vec<PushSubscriptionDto>>),
19 (status = 200, description = "list push subscriptions", body = AppResponseData<Page<PushSubscriptionDto>>),
1920 (status = 404, description = "resource not found", body = AppResponseData<NoData>),
2021 (status = 401, description = "authentication required", body = AppResponseData<NoData>)
2122 )
@@ -24,7 +25,7 @@
2425 State(service): State<UserService>,
2526 session: Session,
2627 Query(query): Query<PageQuery>,
27) -> Result<AppResponseData<Vec<PushSubscriptionDto>>, ApiError> {
28) -> Result<AppResponseData<Page<PushSubscriptionDto>>, ApiError> {
2829 let uid = session::require_uid(&session)?;
2930 let list = service.list_push_subscriptions(uid, query).await?;
3031 Ok(AppResponseData::ok(list))
Modified lib/service/user/src/handlers/list_saved_messages.rs lib/service/user/src/handlers/list_saved_messages.rs +3 −2 View lib/service/user/src/handlers/list_saved_messages.rs
@@ -1,5 +1,6 @@
11use ::session::Session;
22use api::http::{Query, State};
3use api::pagination::Page;
34use api::rest::{AppResponseData, NoData};
45
56use crate::errors::ApiError;
@@ -15,7 +16,7 @@
1516 description = "分页列出当前用户收藏的消息引用。",
1617 params(PageQuery),
1718 responses(
18 (status = 200, description = "list saved messages", body = AppResponseData<Vec<SavedMessageDto>>),
19 (status = 200, description = "list saved messages", body = AppResponseData<Page<SavedMessageDto>>),
1920 (status = 404, description = "resource not found", body = AppResponseData<NoData>),
2021 (status = 401, description = "authentication required", body = AppResponseData<NoData>)
2122 )
@@ -24,7 +25,7 @@
2425 State(service): State<UserService>,
2526 session: Session,
2627 Query(query): Query<PageQuery>,
27) -> Result<AppResponseData<Vec<SavedMessageDto>>, ApiError> {
28) -> Result<AppResponseData<Page<SavedMessageDto>>, ApiError> {
2829 let uid = session::require_uid(&session)?;
2930 let list = service.list_saved_messages(uid, query).await?;
3031 Ok(AppResponseData::ok(list))
Modified lib/service/user/src/handlers/my_reports.rs lib/service/user/src/handlers/my_reports.rs +3 −2 View lib/service/user/src/handlers/my_reports.rs
@@ -1,5 +1,6 @@
11use ::session::Session;
22use api::http::{Query, State};
3use api::pagination::Page;
34use api::rest::{AppResponseData, NoData};
45
56use crate::errors::ApiError;
@@ -15,7 +16,7 @@
1516 description = "分页列出当前用户提交的举报及处理结果。",
1617 params(PageQuery),
1718 responses(
18 (status = 200, description = "my filed reports", body = AppResponseData<Vec<UserReportDto>>),
19 (status = 200, description = "my filed reports", body = AppResponseData<Page<UserReportDto>>),
1920 (status = 404, description = "resource not found", body = AppResponseData<NoData>),
2021 (status = 401, description = "authentication required", body = AppResponseData<NoData>)
2122 )
@@ -24,7 +25,7 @@
2425 State(service): State<UserService>,
2526 session: Session,
2627 Query(query): Query<PageQuery>,
27) -> Result<AppResponseData<Vec<UserReportDto>>, ApiError> {
28) -> Result<AppResponseData<Page<UserReportDto>>, ApiError> {
2829 let uid = session::require_uid(&session)?;
2930 let reports = service.my_reports(uid, query).await?;
3031 Ok(AppResponseData::ok(reports))
Modified lib/service/user/src/handlers/my_spaces.rs lib/service/user/src/handlers/my_spaces.rs +3 −2 View lib/service/user/src/handlers/my_spaces.rs
@@ -1,5 +1,6 @@
11use ::session::Session;
22use api::http::{Query, State};
3use api::pagination::Page;
34use api::rest::{AppResponseData, NoData};
45
56use crate::errors::ApiError;
@@ -15,7 +16,7 @@
1516 description = "列出当前用户激活成员身份的空间及有效权限。",
1617 params(PageQuery),
1718 responses(
18 (status = 200, description = "my spaces", body = AppResponseData<Vec<MySpaceDto>>),
19 (status = 200, description = "my spaces", body = AppResponseData<Page<MySpaceDto>>),
1920 (status = 404, description = "resource not found", body = AppResponseData<NoData>),
2021 (status = 401, description = "authentication required", body = AppResponseData<NoData>)
2122 )
@@ -24,7 +25,7 @@
2425 State(service): State<UserService>,
2526 session: Session,
2627 Query(query): Query<PageQuery>,
27) -> Result<AppResponseData<Vec<MySpaceDto>>, ApiError> {
28) -> Result<AppResponseData<Page<MySpaceDto>>, ApiError> {
2829 let uid = session::require_uid(&session)?;
2930 let spaces = service.my_spaces(uid, query).await?;
3031 Ok(AppResponseData::ok(spaces))
Modified lib/service/user/src/handlers/ssh_key.rs lib/service/user/src/handlers/ssh_key.rs +7 −10 View lib/service/user/src/handlers/ssh_key.rs
@@ -2,8 +2,8 @@
22use crate::services::ssh_key::types::{SshKeyAddRequest, SshKeySummary};
33use api::http::{Json, Path, Query, State};
44use api::middleware::RequestId;
5use api::pagination::{Page, PageQuery};
56use api::rest::{AppResponseData, NoData};
6use auth::share::types::ListQuery;
77use session::Session;
88use uuid::Uuid;
99
@@ -13,17 +13,14 @@
1313 tag = "auth",
1414 operation_id = "auth_ssh_key_list",
1515 summary = "列示 SSH 公钥",
16 description = "按创建时间倒序返回当前账号的公钥,含已吊销与已过期的记录。分页参数由 `page` / `page_size` 控制。查询串不是合法分页参数时由 actix-web 直接返回 400。",
17 params(
18 ("page" = Option<u64>, Query, description = "页码,从 1 开始;默认 1"),
19 ("page_size" = Option<u64>, Query, description = "每页条数,默认 20,最多 100")
20 ),
16 description = "按创建时间倒序返回当前账号的公钥,含已吊销与已过期的记录。使用 `limit` / `offset` 分页;`limit` 必须在 1..=100、`offset` 不得超过 5000,越界返回 400。",
17 params(PageQuery),
2118 responses(
2219 (
2320 status = 200,
2421 description = "公钥列表",
25 body = AppResponseData<Vec<SshKeySummary>>,
26 example = json!({"code":200,"message":"success","data":[{"id":"018f2b1c-4d5e-7f80-9a1b-2c3d4e5f6072","name":"thinkpad","description":"笔记本","algorithm":"ed25519","fingerprint":"SHA256:9pQ2mLk1v3sT6yZx8cV0bN4mR7wQ2eH5jK9aD1sF3gU","public_key":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL9kQ2mXv7Tb1sYz8cV0bN4mR7wQ2eH5jK9aD1sF3gU thinkpad","created_at":"2026-01-02T03:04:05Z","expires_at":null,"last_used_at":null,"revoked_at":null}]})
22 body = AppResponseData<Page<SshKeySummary>>,
23 example = json!({"code":200,"message":"success","data":{"items":[{"id":"018f2b1c-4d5e-7f80-9a1b-2c3d4e5f6072","name":"thinkpad","description":"笔记本","algorithm":"ed25519","fingerprint":"SHA256:9pQ2mLk1v3sT6yZx8cV0bN4mR7wQ2eH5jK9aD1sF3gU","public_key":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL9kQ2mXv7Tb1sYz8cV0bN4mR7wQ2eH5jK9aD1sF3gU thinkpad","created_at":"2026-01-02T03:04:05Z","expires_at":null,"last_used_at":null,"revoked_at":null}],"total":1,"limit":50,"offset":0}})
2724 ),
2825 (
2926 status = 401,
@@ -42,9 +39,9 @@
4239pub async fn ssh_key_list(
4340 State(svc): State<crate::services::service::UserService>,
4441 session: Session,
45 Query(query): Query<ListQuery>,
42 Query(query): Query<PageQuery>,
4643 request_id: RequestId,
47) -> Result<AppResponseData<Vec<SshKeySummary>>, ApiError> {
44) -> Result<AppResponseData<Page<SshKeySummary>>, ApiError> {
4845 let svc = crate::services::ssh_key::SshKeyService::new(svc.accounts.clone());
4946 let keys = svc.list_ssh_keys(session, query, request_id.0).await?;
5047 Ok(AppResponseData::ok(keys))
Modified lib/service/user/src/services/ssh_key/service.rs lib/service/user/src/services/ssh_key/service.rs +15 −11 View lib/service/user/src/services/ssh_key/service.rs
@@ -9,11 +9,13 @@
99use sea_orm::entity::Set;
1010use sea_orm::sea_query::{Expr, ExprTrait};
1111use sea_orm::{
12 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, SqlErr,
13 TransactionTrait,
12 ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
13 QuerySelect, SqlErr, TransactionTrait,
1414};
1515use uuid::Uuid;
1616
17use api::pagination::{Page, PageQuery};
18
1719use crate::services::ssh_key::SshKeyService;
1820use crate::services::ssh_key::types::{
1921 MAX_SSH_KEY_DESCRIPTION_LEN, MAX_SSH_KEY_NAME_LEN, MAX_SSH_PUBLIC_KEY_LEN,
@@ -21,7 +23,6 @@
2123};
2224use auth::errors::{AuthError, AuthResult};
2325use auth::share::crypto;
24use auth::share::types::ListQuery;
2526
2627pub(crate) fn parse_public_key(raw: &str) -> AuthResult<(PublicKeyType, String)> {
2728 let key_line = raw.trim();
@@ -189,22 +190,25 @@
189190 pub async fn list_ssh_keys(
190191 &self,
191192 session: Session,
192 query: ListQuery,
193 query: PageQuery,
193194 _request_id: String,
194 ) -> AuthResult<Vec<SshKeySummary>> {
195 ) -> AuthResult<Page<SshKeySummary>> {
196 let (limit, offset) = query.bounds()?;
195197 let user_id = self
196198 .accounts
197199 .require_session(&session, Some(_request_id.as_str()))
198200 .await?;
199 let records = user_ssh_keys::Entity::find()
200 .filter(user_ssh_keys::Column::UserId.eq(user_id))
201 let select =
202 user_ssh_keys::Entity::find().filter(user_ssh_keys::Column::UserId.eq(user_id));
203 let total = select.clone().count(self.accounts.db().read()).await?;
204 let records = select
201205 .order_by_desc(user_ssh_keys::Column::CreatedAt)
202 .limit(query.limit())
203 .offset(query.offset())
206 .limit(limit)
207 .offset(offset)
204208 .all(self.accounts.db().read())
205209 .await?;
206
207 Ok(records.into_iter().map(summary).collect())
210 let items = records.into_iter().map(summary).collect();
211 Ok(Page::new(items, total, limit, offset))
208212 }
209213
210214 /// 吊销公钥与提高账号 `security_version` 在同一事务里完成:密钥泄露后,账号既有的访问令牌与
Modified lib/service/user/src/services/notifications.rs lib/service/user/src/services/notifications.rs +23 −14 View lib/service/user/src/services/notifications.rs
@@ -1,7 +1,8 @@
11use models::users::user_reports::{self, ReportStatus};
22use models::users::{user_recent_mentions, user_saved_messages};
33use sea_orm::{
4 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set,
4 ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
5 QuerySelect, Set,
56};
67use serde::{Deserialize, Serialize};
78use time::OffsetDateTime;
@@ -10,7 +11,7 @@
1011
1112use crate::errors::UserError;
1213use crate::services::service::UserService;
13use crate::share::page::PageQuery;
14use crate::share::page::{Page, PageQuery};
1415
1516#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1617pub struct RecentMentionDto {
@@ -103,30 +104,38 @@
103104 &self,
104105 uid: i64,
105106 q: PageQuery,
106 ) -> Result<Vec<UserReportDto>, UserError> {
107 let rows = user_reports::Entity::find()
108 .filter(user_reports::Column::ReporterUid.eq(Some(uid)))
107 ) -> Result<Page<UserReportDto>, UserError> {
108 let (limit, offset) = q.bounds()?;
109 let select =
110 user_reports::Entity::find().filter(user_reports::Column::ReporterUid.eq(Some(uid)));
111 let total = select.clone().count(self.database.read()).await?;
112 let rows = select
109113 .order_by_desc(user_reports::Column::CreatedAt)
110 .limit(q.limit())
111 .offset(q.offset())
114 .limit(limit)
115 .offset(offset)
112116 .all(self.database.read())
113117 .await?;
114 Ok(rows.into_iter().map(UserReportDto::from).collect())
118 let items = rows.into_iter().map(UserReportDto::from).collect();
119 Ok(Page::new(items, total, limit, offset))
115120 }
116121
117122 pub async fn list_saved_messages(
118123 &self,
119124 uid: i64,
120125 q: PageQuery,
121 ) -> Result<Vec<SavedMessageDto>, UserError> {
122 let rows = user_saved_messages::Entity::find()
123 .filter(user_saved_messages::Column::UserId.eq(uid))
126 ) -> Result<Page<SavedMessageDto>, UserError> {
127 let (limit, offset) = q.bounds()?;
128 let select =
129 user_saved_messages::Entity::find().filter(user_saved_messages::Column::UserId.eq(uid));
130 let total = select.clone().count(self.database.read()).await?;
131 let rows = select
124132 .order_by_desc(user_saved_messages::Column::SavedAt)
125 .limit(q.limit())
126 .offset(q.offset())
133 .limit(limit)
134 .offset(offset)
127135 .all(self.database.read())
128136 .await?;
129 Ok(rows.into_iter().map(SavedMessageDto::from).collect())
137 let items = rows.into_iter().map(SavedMessageDto::from).collect();
138 Ok(Page::new(items, total, limit, offset))
130139 }
131140
132141 #[tracing::instrument(name = "user.save_message", skip_all, fields(uid = %uid))]
Modified lib/service/user/src/services/profile.rs lib/service/user/src/services/profile.rs +5 −5 View lib/service/user/src/services/profile.rs
@@ -10,7 +10,7 @@
1010
1111use crate::errors::UserError;
1212use crate::services::service::UserService;
13use crate::share::page::PageQuery;
13use crate::share::page::{Page, PageQuery};
1414
1515pub const OWNER_PERMISSIONS: [&str; 9] = [
1616 "view_space",
@@ -116,11 +116,11 @@
116116}
117117
118118impl UserService {
119 pub async fn my_spaces(&self, uid: i64, q: PageQuery) -> Result<Vec<MySpaceDto>, UserError> {
120 let limit = q.limit();
121 let offset = q.offset();
119 pub async fn my_spaces(&self, uid: i64, q: PageQuery) -> Result<Page<MySpaceDto>, UserError> {
120 let (limit, offset) = q.bounds()?;
122121 let mut space_rows = self.access.user_spaces(uid).await?;
123122 space_rows.sort_by(|a, b| a.slug().cmp(b.slug()));
123 let total = space_rows.len() as u64;
124124
125125 let mut out = Vec::new();
126126 for space in space_rows
@@ -145,7 +145,7 @@
145145 permissions: perms,
146146 });
147147 }
148 Ok(out)
148 Ok(Page::new(out, total, limit, offset))
149149 }
150150
151151 pub async fn list_memes(
Modified lib/service/user/src/services/push_subscriptions.rs lib/service/user/src/services/push_subscriptions.rs +13 −8 View lib/service/user/src/services/push_subscriptions.rs
@@ -1,6 +1,7 @@
11use models::users::user_push_subscriptions::{self, PushSubscriptionPlatform};
22use sea_orm::{
3 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set,
3 ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
4 QuerySelect, Set,
45};
56use serde::{Deserialize, Serialize};
67use time::OffsetDateTime;
@@ -9,7 +10,7 @@
910
1011use crate::errors::UserError;
1112use crate::services::service::UserService;
12use crate::share::page::PageQuery;
13use crate::share::page::{Page, PageQuery};
1314
1415#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1516pub struct PushSubscriptionDto {
@@ -48,15 +49,19 @@
4849 &self,
4950 uid: i64,
5051 q: PageQuery,
51 ) -> Result<Vec<PushSubscriptionDto>, UserError> {
52 let rows = user_push_subscriptions::Entity::find()
53 .filter(user_push_subscriptions::Column::UserId.eq(uid))
52 ) -> Result<Page<PushSubscriptionDto>, UserError> {
53 let (limit, offset) = q.bounds()?;
54 let select = user_push_subscriptions::Entity::find()
55 .filter(user_push_subscriptions::Column::UserId.eq(uid));
56 let total = select.clone().count(self.database.read()).await?;
57 let rows = select
5458 .order_by_desc(user_push_subscriptions::Column::CreatedAt)
55 .limit(q.limit())
56 .offset(q.offset())
59 .limit(limit)
60 .offset(offset)
5761 .all(self.database.read())
5862 .await?;
59 Ok(rows.into_iter().map(PushSubscriptionDto::from).collect())
63 let items = rows.into_iter().map(PushSubscriptionDto::from).collect();
64 Ok(Page::new(items, total, limit, offset))
6065 }
6166
6267 #[tracing::instrument(name = "user.upsert_push_subscription", skip_all, fields(uid = %uid))]
Modified lib/service/user/src/errors.rs lib/service/user/src/errors.rs +1 −0 View lib/service/user/src/errors.rs
@@ -32,6 +32,7 @@
3232storage::storage_error_conversion!(UserError);
3333cache::redis_error_conversion!(UserError);
3434api::json_payload_conversion!(UserError);
35api::page_error_conversion!(UserError, BadRequest);
3536
3637api::api_error! {
3738 log: "user",
Modified lib/service/user/src/openapi.rs lib/service/user/src/openapi.rs +5 −0 View lib/service/user/src/openapi.rs
@@ -46,6 +46,11 @@
4646 ),
4747 components(schemas(
4848 api::pagination::PageQuery,
49 api::pagination::Page<MySpaceDto>,
50 api::pagination::Page<PushSubscriptionDto>,
51 api::pagination::Page<UserReportDto>,
52 api::pagination::Page<SavedMessageDto>,
53 api::pagination::Page<crate::services::ssh_key::types::SshKeySummary>,
4954 imaging::layout::ImageVariant,
5055 NoData,
5156 AvatarFormat,
Modified lib/service/webapi/src/openapi.rs lib/service/webapi/src/openapi.rs +8 −4 View lib/service/webapi/src/openapi.rs
@@ -45,10 +45,14 @@
4545 partial updates use `PATCH`.
4646
4747### Pagination
48- Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
49 response `data` is `{ items, total, limit, offset }`.
50- Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
51 response contains `has_more`.
48- 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
49 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
50- `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
51 (`error_code: bad_request`),不会静默截断。
52- 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
53 未知值返回 400,未知参数同样返回 400。
54- chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
55- Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
5256
5357### PATCH semantics (three-state fields)
5458Update commands use `Option<Option<T>>` for clearable fields:
Modified packages/apiclient/auth/auth-challenge.schemas.ts packages/apiclient/auth/auth-challenge.schemas.ts +10 −5 View packages/apiclient/auth/auth-challenge.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-challenge.ts packages/apiclient/auth/auth-challenge.ts +10 −5 View packages/apiclient/auth/auth-challenge.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-login-code-verify.schemas.ts packages/apiclient/auth/auth-login-code-verify.schemas.ts +10 −5 View packages/apiclient/auth/auth-login-code-verify.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-login-code-verify.ts packages/apiclient/auth/auth-login-code-verify.ts +10 −5 View packages/apiclient/auth/auth-login-code-verify.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-login-code.schemas.ts packages/apiclient/auth/auth-login-code.schemas.ts +10 −5 View packages/apiclient/auth/auth-login-code.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-login-code.ts packages/apiclient/auth/auth-login-code.ts +10 −5 View packages/apiclient/auth/auth-login-code.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-login-verify.schemas.ts packages/apiclient/auth/auth-login-verify.schemas.ts +10 −5 View packages/apiclient/auth/auth-login-verify.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-login-verify.ts packages/apiclient/auth/auth-login-verify.ts +10 −5 View packages/apiclient/auth/auth-login-verify.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-login.schemas.ts packages/apiclient/auth/auth-login.schemas.ts +10 −5 View packages/apiclient/auth/auth-login.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-login.ts packages/apiclient/auth/auth-login.ts +10 −5 View packages/apiclient/auth/auth-login.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-logout.schemas.ts packages/apiclient/auth/auth-logout.schemas.ts +10 −5 View packages/apiclient/auth/auth-logout.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-logout.ts packages/apiclient/auth/auth-logout.ts +10 −5 View packages/apiclient/auth/auth-logout.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-me.schemas.ts packages/apiclient/auth/auth-me.schemas.ts +10 −5 View packages/apiclient/auth/auth-me.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-me.ts packages/apiclient/auth/auth-me.ts +10 −5 View packages/apiclient/auth/auth-me.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-password-reset-request.schemas.ts packages/apiclient/auth/auth-password-reset-request.schemas.ts +10 −5 View packages/apiclient/auth/auth-password-reset-request.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-password-reset-request.ts packages/apiclient/auth/auth-password-reset-request.ts +10 −5 View packages/apiclient/auth/auth-password-reset-request.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-password-reset.schemas.ts packages/apiclient/auth/auth-password-reset.schemas.ts +10 −5 View packages/apiclient/auth/auth-password-reset.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-password-reset.ts packages/apiclient/auth/auth-password-reset.ts +10 −5 View packages/apiclient/auth/auth-password-reset.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-register-code.schemas.ts packages/apiclient/auth/auth-register-code.schemas.ts +10 −5 View packages/apiclient/auth/auth-register-code.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-register-code.ts packages/apiclient/auth/auth-register-code.ts +10 −5 View packages/apiclient/auth/auth-register-code.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-register.schemas.ts packages/apiclient/auth/auth-register.schemas.ts +10 −5 View packages/apiclient/auth/auth-register.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-register.ts packages/apiclient/auth/auth-register.ts +10 −5 View packages/apiclient/auth/auth-register.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-ssh-key-add.schemas.ts packages/apiclient/auth/auth-ssh-key-add.schemas.ts +10 −5 View packages/apiclient/auth/auth-ssh-key-add.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-ssh-key-add.ts packages/apiclient/auth/auth-ssh-key-add.ts +10 −5 View packages/apiclient/auth/auth-ssh-key-add.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-ssh-key-delete.schemas.ts packages/apiclient/auth/auth-ssh-key-delete.schemas.ts +10 −5 View packages/apiclient/auth/auth-ssh-key-delete.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-ssh-key-delete.ts packages/apiclient/auth/auth-ssh-key-delete.ts +10 −5 View packages/apiclient/auth/auth-ssh-key-delete.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/auth-ssh-key-list.schemas.ts packages/apiclient/auth/auth-ssh-key-list.schemas.ts +27 −12 View packages/apiclient/auth/auth-ssh-key-list.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
@@ -98,7 +103,7 @@
98103}
99104
100105export type SshKeyAlgorithm = "rsa" | "ed25519" | "ecdsa";
101export type EnvelopeVecSshKeySummaryDataItem = {
106export type EnvelopePageSshKeySummaryDataItemsItem = {
102107 algorithm: SshKeyAlgorithm;
103108 created_at: string;
104109 /** @nullable */
@@ -115,9 +120,19 @@
115120 revoked_at?: string | null;
116121};
117122
118export interface EnvelopeVecSshKeySummary {
123export type EnvelopePageSshKeySummaryData = {
124 items: EnvelopePageSshKeySummaryDataItemsItem[];
125 /** @minimum 0 */
126 limit: number;
127 /** @minimum 0 */
128 offset: number;
129 /** @minimum 0 */
130 total: number;
131};
132
133export interface EnvelopePageSshKeySummary {
119134 code: number;
120 data?: EnvelopeVecSshKeySummaryDataItem[];
135 data?: EnvelopePageSshKeySummaryData;
121136 /** @nullable */
122137 error?: string | null;
123138 /** @nullable */
@@ -128,13 +143,13 @@
128143
129144export type AuthSshKeyListParams = {
130145 /**
131 * 页码,从 1 开始;默认 1
132146 * @minimum 0
147 * @nullable
133148 */
134 page?: number;
149 limit?: number | null;
135150 /**
136 * 每页条数,默认 20,最多 100
137151 * @minimum 0
152 * @nullable
138153 */
139 page_size?: number;
154 offset?: number | null;
140155};
Modified packages/apiclient/auth/auth-ssh-key-list.ts packages/apiclient/auth/auth-ssh-key-list.ts +13 −8 View packages/apiclient/auth/auth-ssh-key-list.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
@@ -89,11 +94,11 @@
8994import type {
9095 AuthSshKeyListParams,
9196 EnvelopeNoData,
92 EnvelopeVecSshKeySummary,
97 EnvelopePageSshKeySummary,
9398} from "./auth-ssh-key-list.schemas";
9499
95100export type authSshKeyListResponse200 = {
96 data: EnvelopeVecSshKeySummary;
101 data: EnvelopePageSshKeySummary;
97102 status: 200;
98103};
99104
@@ -136,7 +141,7 @@
136141};
137142
138143/**
139 * 按创建时间倒序返回当前账号的公钥,含已吊销与已过期的记录。分页参数由 `page` / `page_size` 控制。查询串不是合法分页参数时由 actix-web 直接返回 400。
144 * 按创建时间倒序返回当前账号的公钥,含已吊销与已过期的记录。使用 `limit` / `offset` 分页;`limit` 必须在 1..=100、`offset` 不得超过 5000,越界返回 400。
140145 * @summary 列示 SSH 公钥
141146 */
142147export const authSshKeyList = async (
Modified packages/apiclient/auth/change-password.schemas.ts packages/apiclient/auth/change-password.schemas.ts +10 −5 View packages/apiclient/auth/change-password.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/change-password.ts packages/apiclient/auth/change-password.ts +10 −5 View packages/apiclient/auth/change-password.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/recovery-codes.schemas.ts packages/apiclient/auth/recovery-codes.schemas.ts +10 −5 View packages/apiclient/auth/recovery-codes.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/recovery-codes.ts packages/apiclient/auth/recovery-codes.ts +10 −5 View packages/apiclient/auth/recovery-codes.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/regenerate-recovery-codes.schemas.ts packages/apiclient/auth/regenerate-recovery-codes.schemas.ts +10 −5 View packages/apiclient/auth/regenerate-recovery-codes.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/regenerate-recovery-codes.ts packages/apiclient/auth/regenerate-recovery-codes.ts +10 −5 View packages/apiclient/auth/regenerate-recovery-codes.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/totp-disable.schemas.ts packages/apiclient/auth/totp-disable.schemas.ts +10 −5 View packages/apiclient/auth/totp-disable.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/totp-disable.ts packages/apiclient/auth/totp-disable.ts +10 −5 View packages/apiclient/auth/totp-disable.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/totp-enable.schemas.ts packages/apiclient/auth/totp-enable.schemas.ts +10 −5 View packages/apiclient/auth/totp-enable.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/totp-enable.ts packages/apiclient/auth/totp-enable.ts +10 −5 View packages/apiclient/auth/totp-enable.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/totp-setup.schemas.ts packages/apiclient/auth/totp-setup.schemas.ts +10 −5 View packages/apiclient/auth/totp-setup.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/totp-setup.ts packages/apiclient/auth/totp-setup.ts +10 −5 View packages/apiclient/auth/totp-setup.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/totp-status.schemas.ts packages/apiclient/auth/totp-status.schemas.ts +10 −5 View packages/apiclient/auth/totp-status.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/auth/totp-status.ts packages/apiclient/auth/totp-status.ts +10 −5 View packages/apiclient/auth/totp-status.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
Modified packages/apiclient/index.schemas.ts packages/apiclient/index.schemas.ts +27 −12 View packages/apiclient/index.schemas.ts
@@ -35,10 +35,14 @@
3535 * partial updates use `PATCH`.
3636 *
3737 * ### Pagination
38 * - Offset style: `?limit=&offset=` (default 50, max 100 unless stated),
39 * response `data` is `{ items, total, limit, offset }`.
40 * - Cursor style (chat message/sequence data): `?before_seq=&after_seq=&limit=`,
41 * response contains `has_more`.
38 * - 唯一 offset 风格:`?limit=&offset=`(`limit` 默认 50;`offset` 上限 5000),
39 * 分页响应的 `data` 恒为 `{ items, total, limit, offset }`,字段均为无符号整数。
40 * - `limit` 为 0、超过端点上限或 `offset` 超过 5000 时返回 400
41 * (`error_code: bad_request`),不会静默截断。
42 * - 排序参数为 `?sort=<field>&order=asc|desc`;`sort` 的取值按端点枚举校验,
43 * 未知值返回 400,未知参数同样返回 400。
44 * - chat 消息额外接受 `before_seq` / `after_seq` 作为筛选条件,响应仍是同一 `Page` 形状。
45 * - Git LFS 锁定列表的 `next_cursor` 是 LFS 协议字段,不适用本约定。
4246 *
4347 * ### PATCH semantics (three-state fields)
4448 * Update commands use `Option<Option<T>>` for clearable fields:
@@ -55,7 +59,8 @@
5559 *
5660 * ### Operations
5761 * - `GET /healthz` — liveness probe (no auth, outside `/api/v1`).
58 * - `GET /readyz` — readiness probe: database + redis check, `503` when down.
62 * - `GET /readyz` — readiness probe: database (primary) + redis live checks,
63 * rocketmq + s3 sampled every 10s, `503` when any is down.
5964 * - Runtime metrics are pushed to the configured OTLP collector (30s interval);
6065 * there is no scrape endpoint.
6166 * - Cross-origin browser access is disabled by default; the API expects a
@@ -387,7 +392,7 @@
387392}
388393
389394export type SshKeyAlgorithm = "rsa" | "ed25519" | "ecdsa";
390export type EnvelopeVecSshKeySummaryDataItem = {
395export type EnvelopePageSshKeySummaryDataItemsItem = {
391396 algorithm: SshKeyAlgorithm;
392397 created_at: string;
393398 /** @nullable */
@@ -404,9 +409,19 @@
404409 revoked_at?: string | null;
405410};
406411
407export interface EnvelopeVecSshKeySummary {
412export type EnvelopePageSshKeySummaryData = {
413 items: EnvelopePageSshKeySummaryDataItemsItem[];
414 /** @minimum 0 */
415 limit: number;
416 /** @minimum 0 */
417 offset: number;
418 /** @minimum 0 */
419 total: number;
420};
421
422export interface EnvelopePageSshKeySummary {
408423 code: number;
409 data?: EnvelopeVecSshKeySummaryDataItem[];
424 data?: EnvelopePageSshKeySummaryData;
410425 /** @nullable */
411426 error?: string | null;
412427 /** @nullable */
@@ -454,13 +469,13 @@
454469
455470export type AuthSshKeyListParams = {
456471 /**
457 * 页码,从 1 开始;默认 1
458472 * @minimum 0
473 * @nullable
459474 */
460 page?: number;
475 limit?: number | null;
461476 /**
462 * 每页条数,默认 20,最多 100
463477 * @minimum 0
478 * @nullable
464479 */
465 page_size?: number;
480 offset?: number | null;
466481};
Modified openapi.json openapi.json View openapi.json
Diff too large to display.