refactor(lib): refresh mcp, project and repo crates with new handlers (readme, merge_strategies, sidebar, tree, etc.)
zhenyi committed
Modified lib/mcp/access_key.rs lib/mcp/access_key.rs +6 −3 View lib/mcp/access_key.rs
@@ -54,7 +54,7 @@
5454mod tests {
5555 use super::*;
5656
57 #[test]
57 #[test]
5858 fn compute_token_hash_format() {
5959 let raw = "Tm90LWFjdHVhbC1zZWNyZXRfdXNlZF9vbmx5X2ZvcjpU";
6060 let salt = "abcdef0123456789";
@@ -69,7 +69,10 @@
6969 .collect();
7070 assert_eq!(h, expected);
7171 assert_eq!(h.len(), 64);
72 assert!(h.chars().all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()));
72 assert!(
73 h.chars()
74 .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
75 );
7376 }
7477
7578 #[test]
@@ -80,4 +83,4 @@
8083 assert_eq!(token_last_eight("xxxxxxxxxxxxabcdEFGH"), "abcdEFGH");
8184 assert_eq!(token_last_eight("0123456789ABCDEFGH"), "ABCDEFGH");
8285 }
83}
No newline at end of file
86}
Modified lib/mcp/auth.rs lib/mcp/auth.rs +35 −23 View lib/mcp/auth.rs
@@ -1,12 +1,12 @@
11//! supplied via `X-Mcp-Internal-Key` or `Authorization: Bearer <secret>`.
22use std::net::{IpAddr, SocketAddr};
33
4use axum::Json;
45use axum::extract::{ConnectInfo, Request, State};
56use axum::http::header::AUTHORIZATION;
67use axum::http::{HeaderMap, StatusCode};
78use axum::middleware::Next;
89use axum::response::{IntoResponse, Response};
9use axum::Json;
1010use serde_json::json;
1111use uuid::Uuid;
1212
@@ -18,8 +18,8 @@
1818
1919#[derive(Debug, Clone)]
2020pub enum McpPrincipal {
21 External { user_id: Uuid, scope: DbScope },
22 Internal,
21 External { user_id: Uuid, scope: DbScope },
22 Internal,
2323}
2424
2525#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -45,7 +45,7 @@
4545 }
4646 }
4747
48 pub fn actor_uuid(&self, svc: &McpService) -> Result<Uuid, McpError> {
48 pub fn actor_uuid(&self, svc: &McpService) -> Result<Uuid, McpError> {
4949 match self {
5050 McpPrincipal::External { user_id, .. } => Ok(*user_id),
5151 McpPrincipal::Internal => svc.system_user_uuid(),
@@ -75,7 +75,10 @@
7575}
7676
7777fn read_presented(headers: &HeaderMap) -> Option<&str> {
78 if let Some(hk) = headers.get("x-mcp-internal-key").and_then(|v| v.to_str().ok()) {
78 if let Some(hk) = headers
79 .get("x-mcp-internal-key")
80 .and_then(|v| v.to_str().ok())
81 {
7982 if !hk.is_empty() {
8083 return Some(hk);
8184 }
@@ -104,22 +107,37 @@
104107 }
105108}
106109
107async fn check_external_key(svc: &McpService, headers: &HeaderMap) -> Result<McpPrincipal, McpError> {
110async fn check_external_key(
111 svc: &McpService,
112 headers: &HeaderMap,
113) -> Result<McpPrincipal, McpError> {
108114 let presented = read_presented(headers).ok_or(AuthError::InvalidCredentials)?;
109115 let (user_id, scope) = access_key::verify_access_key(&svc.db, presented).await?;
110116 Ok(McpPrincipal::External { user_id, scope })
111117}
112118
113119fn unauthorized() -> Response {
114 (StatusCode::UNAUTHORIZED, Json(json!({"error": "mcp unauthorized"}))).into_response()
120 (
121 StatusCode::UNAUTHORIZED,
122 Json(json!({"error": "mcp unauthorized"})),
123 )
124 .into_response()
115125}
116126
117127fn forbidden() -> Response {
118 (StatusCode::FORBIDDEN, Json(json!({"error": "mcp ip not allowed"}))).into_response()
128 (
129 StatusCode::FORBIDDEN,
130 Json(json!({"error": "mcp ip not allowed"})),
131 )
132 .into_response()
119133}
120134
121135fn internal_error() -> Response {
122 (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "mcp internal"}))).into_response()
136 (
137 StatusCode::INTERNAL_SERVER_ERROR,
138 Json(json!({"error": "mcp internal"})),
139 )
140 .into_response()
123141}
124142
125143pub async fn dual_track_auth(
@@ -203,10 +221,7 @@
203221 let peer: SocketAddr = "127.0.0.1:5555".parse().unwrap();
204222 let mut headers = HeaderMap::new();
205223 headers.insert("x-real-ip", HeaderValue::from_static("10.0.0.1"));
206 assert_eq!(
207 source_ip(&headers, peer, true),
208 IpAddr::from([10, 0, 0, 1])
209 );
224 assert_eq!(source_ip(&headers, peer, true), IpAddr::from([10, 0, 0, 1]));
210225 }
211226
212227 #[test]
@@ -217,10 +232,7 @@
217232 "x-forwarded-for",
218233 HeaderValue::from_static("10.0.0.5, 10.0.0.6, 10.0.0.7"),
219234 );
220 assert_eq!(
221 source_ip(&headers, peer, true),
222 IpAddr::from([10, 0, 0, 5])
223 );
235 assert_eq!(source_ip(&headers, peer, true), IpAddr::from([10, 0, 0, 5]));
224236 }
225237
226238 #[test]
@@ -228,16 +240,16 @@
228240 let peer: SocketAddr = "10.0.0.1:5555".parse().unwrap();
229241 let mut headers = HeaderMap::new();
230242 headers.insert("x-real-ip", HeaderValue::from_static("not-an-ip"));
231 assert_eq!(
232 source_ip(&headers, peer, true),
233 IpAddr::from([10, 0, 0, 1])
234 );
243 assert_eq!(source_ip(&headers, peer, true), IpAddr::from([10, 0, 0, 1]));
235244 }
236245
237246 #[test]
238247 fn read_presented_accepts_internal_header() {
239248 let mut headers = HeaderMap::new();
240 headers.insert("x-mcp-internal-key", HeaderValue::from_static("shared-secret"));
249 headers.insert(
250 "x-mcp-internal-key",
251 HeaderValue::from_static("shared-secret"),
252 );
241253 assert_eq!(read_presented(&headers), Some("shared-secret"));
242254 }
243255
@@ -285,4 +297,4 @@
285297 assert!(internal.tool_allowed(false));
286298 assert!(internal.tool_allowed(true));
287299 }
288}
No newline at end of file
300}
Modified lib/mcp/error.rs lib/mcp/error.rs +1 −1 View lib/mcp/error.rs
@@ -78,4 +78,4 @@
7878 let m: McpError = a.into();
7979 assert!(matches!(m, McpError::Auth(AuthError::InvalidCredentials)));
8080 }
81}
No newline at end of file
81}
Modified lib/mcp/ip_filter.rs lib/mcp/ip_filter.rs +5 −5 View lib/mcp/ip_filter.rs
@@ -37,7 +37,7 @@
3737}
3838
3939impl IpFilter {
40 pub fn from_settings(extra: &[String]) -> Result<Self, McpError> {
40 pub fn from_settings(extra: &[String]) -> Result<Self, McpError> {
4141 let mut nets: Vec<IpNet> = BUILTIN_INTERNAL_CIDRS
4242 .iter()
4343 .map(|c| {
@@ -88,8 +88,8 @@
8888 let f = IpFilter::from_settings(&[]).unwrap();
8989 assert!(!f.contains(ip("1.1.1.1")));
9090 assert!(!f.contains(ip("8.8.8.8")));
91 assert!(!f.contains(ip("172.32.0.1")));
92 assert!(!f.contains(ip("11.0.0.1")));
91 assert!(!f.contains(ip("172.32.0.1")));
92 assert!(!f.contains(ip("11.0.0.1")));
9393 assert!(!f.contains(ip("172.15.255.255")));
9494 }
9595
@@ -117,7 +117,7 @@
117117 fn kubernetes_cidrs_included() {
118118 let f = IpFilter::from_settings(&[]).unwrap();
119119 assert!(f.contains(ip("10.96.0.1")));
120 assert!(f.contains(ip("10.96.204.10")));
120 assert!(f.contains(ip("10.96.204.10")));
121121 assert!(f.contains(ip("10.244.0.1")));
122122 assert!(f.contains(ip("10.100.0.5")));
123123 }
@@ -155,4 +155,4 @@
155155 let f = IpFilter::from_settings(&[]).unwrap();
156156 assert_eq!(f.net_count(), BUILTIN_INTERNAL_CIDRS.len());
157157 }
158}
No newline at end of file
158}
Modified lib/mcp/lib.rs lib/mcp/lib.rs +1 −1 View lib/mcp/lib.rs
@@ -12,4 +12,4 @@
1212pub use auth::{McpPrincipal, McpTrack};
1313pub use error::{AuthError, McpError};
1414pub use router::mcp_router;
15pub use service::McpService;
No newline at end of file
15pub use service::McpService;
Modified lib/mcp/router.rs lib/mcp/router.rs +10 −9 View lib/mcp/router.rs
@@ -1,10 +1,10 @@
11use std::time::Duration;
22
3use axum::Router;
34use axum::extract::DefaultBodyLimit;
45use axum::middleware;
5use axum::Router;
66use rmcp::transport::streamable_http_server::{
7 session::local::LocalSessionManager, StreamableHttpServerConfig, StreamableHttpService,
7 StreamableHttpServerConfig, StreamableHttpService, session::local::LocalSessionManager,
88};
99use tokio_util::sync::CancellationToken;
1010
@@ -36,12 +36,13 @@
3636 auth::dual_track_auth,
3737 ));
3838
39 let internal = Router::new()
40 .nest_service(&internal_path, http_svc)
41 .layer(middleware::from_fn_with_state(
42 svc.clone(),
43 auth::internal_only_auth,
44 ));
39 let internal =
40 Router::new()
41 .nest_service(&internal_path, http_svc)
42 .layer(middleware::from_fn_with_state(
43 svc.clone(),
44 auth::internal_only_auth,
45 ));
4546
4647 Router::new()
4748 .merge(external)
@@ -71,4 +72,4 @@
7172 .with_sse_keep_alive(Some(Duration::from_secs(15)))
7273 .with_cancellation_token(cancel),
7374 )
74}
No newline at end of file
75}
Modified lib/mcp/server.rs lib/mcp/server.rs +21 −49 View lib/mcp/server.rs
@@ -9,7 +9,7 @@
99use rmcp::handler::server::{router::tool::ToolRouter, wrapper::Parameters};
1010use rmcp::model::*;
1111use rmcp::service::RequestContext;
12use rmcp::{tool, tool_handler, tool_router, ErrorData as McpErr, RoleServer, ServerHandler};
12use rmcp::{ErrorData as McpErr, RoleServer, ServerHandler, tool, tool_handler, tool_router};
1313use space::features::SpaceService;
1414use uuid::Uuid;
1515
@@ -56,7 +56,6 @@
5656
5757#[tool_router]
5858impl McpServer {
59
6059 fn principal(ctx: &RequestContext<RoleServer>) -> Result<McpPrincipal, McpErr> {
6160 let parts = ctx
6261 .extensions
@@ -69,19 +68,12 @@
6968 .ok_or_else(|| McpErr::internal_error("mcp auth context missing", None))
7069 }
7170
72 fn actor(
73 principal: &McpPrincipal,
74 settings: &McpSettings,
75 ) -> Result<Uuid, McpErr> {
76 Self::actor_via(principal, settings).map_err(|e| {
77 McpErr::invalid_request(format!("actor resolution failed: {e}"), None)
78 })
71 fn actor(principal: &McpPrincipal, settings: &McpSettings) -> Result<Uuid, McpErr> {
72 Self::actor_via(principal, settings)
73 .map_err(|e| McpErr::invalid_request(format!("actor resolution failed: {e}"), None))
7974 }
8075
81 fn actor_via(
82 principal: &McpPrincipal,
83 settings: &McpSettings,
84 ) -> Result<Uuid, String> {
76 fn actor_via(principal: &McpPrincipal, settings: &McpSettings) -> Result<Uuid, String> {
8577 match principal {
8678 McpPrincipal::External { user_id, .. } => Ok(*user_id),
8779 McpPrincipal::Internal => settings
@@ -106,10 +98,8 @@
10698 }
10799
108100 fn to_text<T: serde::Serialize>(value: &T) -> Result<String, McpErr> {
109 serde_json::to_string(value).map_err(|e| McpErr::internal_error(
110 format!("serialization: {e}"),
111 None,
112 ))
101 serde_json::to_string(value)
102 .map_err(|e| McpErr::internal_error(format!("serialization: {e}"), None))
113103 }
114104
115105 fn tool_err(tool: &str, e: impl std::fmt::Display) -> McpErr {
@@ -120,7 +110,6 @@
120110 McpErr::invalid_params(e, None)
121111 }
122112
123
124113 #[tool(
125114 name = "repo.list_repos",
126115 description = "List repositories visible to the caller. Optional filters: channel_id, q (name substring), is_private, is_archived, plus pagination (limit, offset)."
@@ -133,9 +122,7 @@
133122 let principal = Self::principal(&ctx)?;
134123 Self::scope_gate(&principal, false)?;
135124 let actor = Self::actor(&principal, &self.settings)?;
136 let query = args
137 .into_query()
138 .map_err(Self::bad_arg)?;
125 let query = args.into_query().map_err(Self::bad_arg)?;
139126 let page = repo::features::lifecycle::usecase::list_repos(&self.repo, actor, query)
140127 .await
141128 .map_err(|e| Self::tool_err("repo.list_repos", e))?;
@@ -157,19 +144,14 @@
157144 Self::scope_gate(&principal, false)?;
158145 let actor = Self::actor(&principal, &self.settings)?;
159146 let (channel_id, repo_id) = args.parsed().map_err(Self::bad_arg)?;
160 let view = repo::features::lifecycle::usecase::get_repo(
161 &self.repo,
162 actor,
163 channel_id,
164 repo_id,
165 )
166 .await
167 .map_err(|e| Self::tool_err("repo.get_repo", e))?;
147 let view =
148 repo::features::lifecycle::usecase::get_repo(&self.repo, actor, channel_id, repo_id)
149 .await
150 .map_err(|e| Self::tool_err("repo.get_repo", e))?;
168151 Ok(CallToolResult::success(vec![ContentBlock::text(
169152 Self::to_text(&view)?,
170153 )]))
171154 }
172
173155
174156 #[tool(
175157 name = "issue.list_issues",
@@ -184,14 +166,10 @@
184166 Self::scope_gate(&principal, false)?;
185167 let actor = Self::actor(&principal, &self.settings)?;
186168 let (channel_id, query) = args.into_parts().map_err(Self::bad_arg)?;
187 let page = issues::features::issue::usecase::list_issues(
188 &self.issues,
189 actor,
190 channel_id,
191 query,
192 )
193 .await
194 .map_err(|e| Self::tool_err("issue.list_issues", e))?;
169 let page =
170 issues::features::issue::usecase::list_issues(&self.issues, actor, channel_id, query)
171 .await
172 .map_err(|e| Self::tool_err("issue.list_issues", e))?;
195173 Ok(CallToolResult::success(vec![ContentBlock::text(
196174 Self::to_text(&page)?,
197175 )]))
@@ -210,14 +188,10 @@
210188 Self::scope_gate(&principal, false)?;
211189 let actor = Self::actor(&principal, &self.settings)?;
212190 let (channel_id, issue_id) = args.parsed().map_err(Self::bad_arg)?;
213 let view = issues::features::issue::usecase::get_issue(
214 &self.issues,
215 actor,
216 channel_id,
217 issue_id,
218 )
219 .await
220 .map_err(|e| Self::tool_err("issue.get_issue", e))?;
191 let view =
192 issues::features::issue::usecase::get_issue(&self.issues, actor, channel_id, issue_id)
193 .await
194 .map_err(|e| Self::tool_err("issue.get_issue", e))?;
221195 Ok(CallToolResult::success(vec![ContentBlock::text(
222196 Self::to_text(&view)?,
223197 )]))
@@ -245,7 +219,6 @@
245219 )]))
246220 }
247221
248
249222 #[tool(
250223 name = "project.list_projects",
251224 description = "List projects in a space. Filters: state, lead_id, q, page/per_page."
@@ -291,7 +264,6 @@
291264 Self::to_text(&view)?,
292265 )]))
293266 }
294
295267
296268 #[tool(
297269 name = "space.list_spaces",
@@ -338,4 +310,4 @@
338310 Self::to_text(&view)?,
339311 )]))
340312 }
341}
No newline at end of file
313}
Modified lib/mcp/service.rs lib/mcp/service.rs +4 −5 View lib/mcp/service.rs
@@ -48,14 +48,13 @@
4848 })
4949 }
5050
51 pub fn system_user_uuid(&self) -> Result<Uuid, McpError> {
51 pub fn system_user_uuid(&self) -> Result<Uuid, McpError> {
5252 let s = self
5353 .settings
5454 .system_user_id
5555 .as_deref()
5656 .ok_or(McpError::InternalNotConfigured)?;
57 Uuid::parse_str(s).map_err(|_| {
58 McpError::InvalidConfig("mcp.system_user_id is not a valid UUID".into())
59 })
57 Uuid::parse_str(s)
58 .map_err(|_| McpError::InvalidConfig("mcp.system_user_id is not a valid UUID".into()))
6059 }
61}
No newline at end of file
60}
Modified lib/mcp/service_disabled.rs lib/mcp/service_disabled.rs +1 −1 View lib/mcp/service_disabled.rs
@@ -8,4 +8,4 @@
88 axum::Json(serde_json::json!({"error": "mcp disabled by configuration"})),
99 )
1010 .into_response()
11}
No newline at end of file
11}
Modified lib/mcp/tools.rs lib/mcp/tools.rs +6 −12 View lib/mcp/tools.rs
@@ -2,7 +2,6 @@
22use serde::{Deserialize, Serialize};
33use uuid::Uuid;
44
5
65#[derive(Debug, Clone, Default, Serialize, Deserialize, schemars::JsonSchema)]
76pub struct ListRepoArgs {
87 #[schemars(description = "optional channel UUID filter")]
@@ -55,7 +54,6 @@
5554 ))
5655 }
5756}
58
5957
6058#[derive(Debug, Clone, Default, Serialize, Deserialize, schemars::JsonSchema)]
6159pub struct ListIssueArgs {
@@ -69,9 +67,7 @@
6967}
7068
7169impl ListIssueArgs {
72 pub fn into_parts(
73 self,
74 ) -> Result<(Uuid, export::issues::issue::ListIssueQuery), String> {
70 pub fn into_parts(self) -> Result<(Uuid, export::issues::issue::ListIssueQuery), String> {
7571 let channel_id = parse_uuid(&self.channel_id, "channel_id")?;
7672 let author_id = match self.author_id.as_deref() {
7773 Some(s) if !s.is_empty() => Some(parse_uuid(s, "author_id")?),
@@ -129,9 +125,7 @@
129125}
130126
131127impl CreateIssueArgs {
132 pub fn into_parts(
133 self,
134 ) -> Result<(Uuid, export::issues::issue::CreateIssueOption), String> {
128 pub fn into_parts(self) -> Result<(Uuid, export::issues::issue::CreateIssueOption), String> {
135129 let channel_id = parse_uuid(&self.channel_id, "channel_id")?;
136130 let milestone_id = match self.milestone_id.as_deref() {
137131 Some(s) if !s.is_empty() => Some(parse_uuid(s, "milestone_id")?),
@@ -146,12 +140,13 @@
146140 Ok((channel_id, input))
147141 }
148142}
149
150143
151144#[derive(Debug, Clone, Default, Serialize, Deserialize, schemars::JsonSchema)]
152145pub struct ListProjectArgs {
153146 pub space_id: String,
154 #[schemars(description = "filter by project state (e.g. \"Backlog\", \"Planned\", \"InProgress\", \"Paused\", \"Completed\", \"Cancelled\")")]
147 #[schemars(
148 description = "filter by project state (e.g. \"Backlog\", \"Planned\", \"InProgress\", \"Paused\", \"Completed\", \"Cancelled\")"
149 )]
155150 pub state: Option<String>,
156151 pub lead_id: Option<String>,
157152 pub q: Option<String>,
@@ -209,7 +204,6 @@
209204 ))
210205 }
211206}
212
213207
214208#[derive(Debug, Clone, Default, Serialize, Deserialize, schemars::JsonSchema)]
215209pub struct ListSpaceArgs {
@@ -230,4 +224,4 @@
230224 pub fn parsed(&self) -> Result<Uuid, String> {
231225 parse_uuid(&self.space_id, "space_id")
232226 }
233}
No newline at end of file
227}
Modified lib/project/features/activities/usecase.rs lib/project/features/activities/usecase.rs +13 −3 View lib/project/features/activities/usecase.rs
@@ -1,5 +1,7 @@
11use db::project::project_activities as activity_entity;
22use db::project::project_activities::Column as Ac;
3use db::user::users_loader::load_users;
4use export::user::basic::UserBasicInfo;
35use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder};
46use uuid::Uuid;
57
@@ -38,7 +40,15 @@
3840 let paginator = query.paginate(&reader, per_page);
3941 let total = paginator.num_items().await?;
4042 let rows = paginator.fetch_page(offset / per_page).await?;
41 let items = rows.iter().map(activity_view).collect();
43 let actor_ids: Vec<Uuid> = rows.iter().map(|r| r.actor_id).collect();
44 let users = load_users(&svc.db, &actor_ids).await?;
45 let items = rows
46 .into_iter()
47 .map(|r| {
48 let actor_id = r.actor_id;
49 activity_view(&r, users.get(&actor_id).map(UserBasicInfo::from))
50 })
51 .collect();
4252 Ok(ActivityPage { total, items })
4353 }
4454 .await;
@@ -46,11 +56,11 @@
4656 result
4757}
4858
49fn activity_view(m: &activity_entity::Model) -> ActivityView {
59fn activity_view(m: &activity_entity::Model, actor: Option<UserBasicInfo>) -> ActivityView {
5060 ActivityView {
5161 id: m.id,
5262 project_id: m.project_id,
53 actor_id: m.actor_id,
63 actor,
5464 verb: m.verb.clone(),
5565 target_kind: m.target_kind.clone(),
5666 target_id: m.target_id,
Modified lib/project/features/documents/usecase.rs lib/project/features/documents/usecase.rs +41 −7 View lib/project/features/documents/usecase.rs
@@ -1,5 +1,7 @@
11use db::project::project_documents as doc_entity;
22use db::project::project_documents::Column as Dc;
3use db::user::users_loader::{load_user, load_users};
4use export::user::basic::UserBasicInfo;
35use sea_orm::{
46 ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, Set,
57};
@@ -72,7 +74,8 @@
7274 None,
7375 )
7476 .await;
75 Ok(document_view(&row))
77 let creator = load_user(&svc.db, actor).await?.map(UserBasicInfo::from);
78 Ok(document_view(&row, creator.clone(), creator.clone()))
7679 }
7780 .await;
7881 metrics::record_outcome(metrics::FEATURE_PROJECT_DOCS_CREATE, &result);
@@ -94,7 +97,20 @@
9497 .order_by_asc(Dc::Title)
9598 .all(&svc.db.reader())
9699 .await?;
97 let items = rows.iter().map(document_view).collect::<Vec<_>>();
100 let mut ids: Vec<Uuid> = rows.iter().map(|r| r.creator_id).collect();
101 ids.extend(rows.iter().filter_map(|r| r.last_editor_id));
102 let users = load_users(&svc.db, &ids).await?;
103 let items = rows
104 .iter()
105 .map(|r| {
106 document_view(
107 r,
108 users.get(&r.creator_id).map(UserBasicInfo::from),
109 r.last_editor_id
110 .and_then(|id| users.get(&id).map(UserBasicInfo::from)),
111 )
112 })
113 .collect::<Vec<_>>();
98114 let total = items.len() as u64;
99115 Ok(DocumentPage { total, items })
100116 }
@@ -121,7 +137,14 @@
121137 .ok_or_else(|| {
122138 ProjectError::NotFound(format!("document {doc_id} in project {project_id}"))
123139 })?;
124 Ok(document_view(&row))
140 let creator = load_user(&svc.db, row.creator_id)
141 .await?
142 .map(UserBasicInfo::from);
143 let last_editor = match row.last_editor_id {
144 Some(id) => load_user(&svc.db, id).await?.map(UserBasicInfo::from),
145 None => None,
146 };
147 Ok(document_view(&row, creator, last_editor))
125148 }
126149 .await;
127150 metrics::record_outcome(metrics::FEATURE_PROJECT_DOCS_GET, &result);
@@ -201,7 +224,14 @@
201224 None,
202225 )
203226 .await;
204 Ok(document_view(&updated))
227 let creator = load_user(&svc.db, updated.creator_id)
228 .await?
229 .map(UserBasicInfo::from);
230 let last_editor = match updated.last_editor_id {
231 Some(id) => load_user(&svc.db, id).await?.map(UserBasicInfo::from),
232 None => None,
233 };
234 Ok(document_view(&updated, creator, last_editor))
205235 }
206236 .await;
207237 metrics::record_outcome(metrics::FEATURE_PROJECT_DOCS_UPDATE, &result);
@@ -254,13 +284,17 @@
254284 result
255285}
256286
257fn document_view(m: &doc_entity::Model) -> DocumentView {
287fn document_view(
288 m: &doc_entity::Model,
289 creator: Option<UserBasicInfo>,
290 last_editor: Option<UserBasicInfo>,
291) -> DocumentView {
258292 DocumentView {
259293 id: m.id,
260294 project_id: m.project_id,
261295 parent_id: m.parent_id,
262 creator_id: m.creator_id,
263 last_editor_id: m.last_editor_id,
296 creator,
297 last_editor,
264298 title: m.title.clone(),
265299 slug: m.slug.clone(),
266300 content: m.content.clone(),
Modified lib/project/features/favorites/usecase.rs lib/project/features/favorites/usecase.rs +12 −4 View lib/project/features/favorites/usecase.rs
@@ -1,5 +1,7 @@
11use db::project::project_favorites as fav_entity;
22use db::project::project_favorites::Column as Fc;
3use db::user::users_loader::{load_user, load_users};
4use export::user::basic::UserBasicInfo;
35use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
46use time::OffsetDateTime;
57use uuid::Uuid;
@@ -47,7 +49,8 @@
4749
4850 let _ = svc.cache.invalidate_favorites(actor).await;
4951 log_activity(svc, project_id, actor, "favorited", "favorite", None, None).await;
50 Ok(favorite_view(&row))
52 let user = load_user(&svc.db, actor).await?.map(UserBasicInfo::from);
53 Ok(favorite_view(&row, user))
5154 }
5255 .await;
5356 metrics::record_outcome(metrics::FEATURE_PROJECT_FAVORITES_ADD, &result);
@@ -69,7 +72,12 @@
6972 .order_by_desc(Fc::CreatedAt)
7073 .all(&svc.db.reader())
7174 .await?;
72 let items = rows.iter().map(favorite_view).collect::<Vec<_>>();
75 let user_ids: Vec<Uuid> = rows.iter().map(|r| r.user_id).collect();
76 let users = load_users(&svc.db, &user_ids).await?;
77 let items = rows
78 .iter()
79 .map(|r| favorite_view(r, users.get(&r.user_id).map(UserBasicInfo::from)))
80 .collect::<Vec<_>>();
7381 let total = items.len() as u64;
7482 Ok(FavoritePage { total, items })
7583 }
@@ -119,11 +127,11 @@
119127 result
120128}
121129
122fn favorite_view(m: &fav_entity::Model) -> FavoriteView {
130fn favorite_view(m: &fav_entity::Model, user: Option<UserBasicInfo>) -> FavoriteView {
123131 FavoriteView {
124132 id: m.id,
125133 project_id: m.project_id,
126 user_id: m.user_id,
134 user,
127135 folder: m.folder.clone(),
128136 created_at: m.created_at,
129137 }
Modified lib/project/features/lifecycle/usecase.rs lib/project/features/lifecycle/usecase.rs +52 −7 View lib/project/features/lifecycle/usecase.rs
@@ -4,6 +4,8 @@
44use db::project::project_members as member_entity;
55use db::project::project_members::ProjectMemberRole;
66use db::project::project_state::ProjectState as DbState;
7use db::user::users_loader::{load_user, load_users};
8use export::user::basic::UserBasicInfo;
79use sea_orm::{ActiveModelTrait, Set};
810use space::error::SpaceError;
911use space::features::channels::usecase::ensure_channel_for_resource;
@@ -108,7 +110,12 @@
108110
109111 log_activity(svc, id, actor, "created", "self", None, None).await;
110112
111 Ok(view_from(&inserted))
113 let creator = load_user(&svc.db, actor).await?.map(UserBasicInfo::from);
114 let lead = match inserted.lead_id {
115 Some(id) => load_user(&svc.db, id).await?.map(UserBasicInfo::from),
116 None => None,
117 };
118 Ok(view_from(&inserted, creator, lead))
112119 }
113120 .await;
114121 metrics::record_outcome(metrics::FEATURE_PROJECTS_CREATE, &result);
@@ -130,7 +137,17 @@
130137 .one(&svc.db.reader())
131138 .await?
132139 .ok_or_else(|| ProjectError::NotFound(format!("project {project_id}")))?;
133 Ok(view_from(&row))
140 let mut ids = vec![row.creator_id];
141 if let Some(id) = row.lead_id {
142 ids.push(id);
143 }
144 let users = load_users(&svc.db, &ids).await?;
145 Ok(view_from(
146 &row,
147 users.get(&row.creator_id).map(UserBasicInfo::from),
148 row.lead_id
149 .and_then(|id| users.get(&id).map(UserBasicInfo::from)),
150 ))
134151 }
135152 .await;
136153 metrics::record_outcome(metrics::FEATURE_PROJECTS_GET, &result);
@@ -168,7 +185,20 @@
168185 let paginator = query.paginate(&reader, per_page);
169186 let total = paginator.num_items().await?;
170187 let rows = paginator.fetch_page(offset / per_page).await?;
171 let items = rows.iter().map(view_from).collect();
188 let mut ids: Vec<Uuid> = rows.iter().map(|r| r.creator_id).collect();
189 ids.extend(rows.iter().filter_map(|r| r.lead_id));
190 let users = load_users(&svc.db, &ids).await?;
191 let items = rows
192 .into_iter()
193 .map(|r| {
194 view_from(
195 &r,
196 users.get(&r.creator_id).map(UserBasicInfo::from),
197 r.lead_id
198 .and_then(|id| users.get(&id).map(UserBasicInfo::from)),
199 )
200 })
201 .collect();
172202 Ok(ProjectPage { total, items })
173203 }
174204 .await;
@@ -242,7 +272,18 @@
242272 let _ = svc.cache.invalidate_project(project_id).await;
243273
244274 log_activity(svc, project_id, actor, "updated", "self", None, None).await;
245 Ok(view_from(&updated))
275 let mut ids = vec![updated.creator_id];
276 if let Some(id) = updated.lead_id {
277 ids.push(id);
278 }
279 let users = load_users(&svc.db, &ids).await?;
280 Ok(view_from(
281 &updated,
282 users.get(&updated.creator_id).map(UserBasicInfo::from),
283 updated
284 .lead_id
285 .and_then(|id| users.get(&id).map(UserBasicInfo::from)),
286 ))
246287 }
247288 .await;
248289 metrics::record_outcome(metrics::FEATURE_PROJECTS_UPDATE, &result);
@@ -319,12 +360,16 @@
319360 .await;
320361}
321362
322pub fn view_from(m: &project_entity::Model) -> ProjectView {
363pub fn view_from(
364 m: &project_entity::Model,
365 creator: Option<UserBasicInfo>,
366 lead: Option<UserBasicInfo>,
367) -> ProjectView {
323368 ProjectView {
324369 id: m.id,
325370 space_id: m.space_id,
326 creator_id: m.creator_id,
327 lead_id: m.lead_id,
371 creator,
372 lead,
328373 name: m.name.clone(),
329374 description: m.description.clone(),
330375 icon: m.icon.clone(),
Modified lib/project/features/members/types.rs lib/project/features/members/types.rs +15 −1 View lib/project/features/members/types.rs
@@ -1,4 +1,5 @@
11use db::project::project_members::ProjectMemberRole as DbRole;
2use export::user::basic::UserBasicInfo;
23use serde::{Deserialize, Serialize};
34use time::OffsetDateTime;
45use utoipa::ToSchema;
@@ -57,12 +58,25 @@
5758pub struct MemberView {
5859 pub id: Uuid,
5960 pub project_id: Uuid,
60 pub user_id: Uuid,
61 pub user: Option<UserBasicInfo>,
6162 pub role: MemberRole,
6263 pub joined_at: OffsetDateTime,
6364 pub updated_at: Option<OffsetDateTime>,
6465}
6566
67impl MemberView {
68 pub fn from_model(m: db::project::project_members::Model, user: Option<UserBasicInfo>) -> Self {
69 Self {
70 id: m.id,
71 project_id: m.project_id,
72 user,
73 role: m.role.into(),
74 joined_at: m.joined_at,
75 updated_at: m.updated_at,
76 }
77 }
78}
79
6680#[derive(Debug, Clone, Serialize, ToSchema)]
6781pub struct MemberPage {
6882 pub total: u64,
Modified lib/project/features/members/usecase.rs lib/project/features/members/usecase.rs +25 −6 View lib/project/features/members/usecase.rs
@@ -1,4 +1,6 @@
11use db::project::project_members::{Column as Mc, ProjectMemberRole as DbRole};
2use db::user::users_loader::{load_user, load_users};
3use export::user::basic::UserBasicInfo;
24use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
35use time::OffsetDateTime;
46use uuid::Uuid;
@@ -69,7 +71,10 @@
6971 None,
7072 )
7173 .await;
72 Ok(member_view(&row))
74 let user = load_user(&svc.db, row.user_id)
75 .await?
76 .map(UserBasicInfo::from);
77 Ok(member_view(&row, user))
7378 }
7479 .await;
7580 metrics::record_outcome(metrics::FEATURE_PROJECT_MEMBERS_ADD, &result);
@@ -91,7 +96,15 @@
9196 .order_by_desc(Mc::JoinedAt)
9297 .all(&svc.db.reader())
9398 .await?;
94 let items = rows.iter().map(member_view).collect::<Vec<_>>();
99 let user_ids: Vec<Uuid> = rows.iter().map(|r| r.user_id).collect();
100 let users = load_users(&svc.db, &user_ids).await?;
101 let items = rows
102 .into_iter()
103 .map(|r| {
104 let user_id = r.user_id;
105 member_view(&r, users.get(&user_id).map(UserBasicInfo::from))
106 })
107 .collect::<Vec<_>>();
95108 let total = items.len() as u64;
96109 Ok(MemberPage { total, items })
97110 }
@@ -119,7 +132,10 @@
119132 .ok_or_else(|| {
120133 ProjectError::NotFound(format!("member {user_id} in project {project_id}"))
121134 })?;
122 Ok(member_view(&row))
135 let user = load_user(&svc.db, row.user_id)
136 .await?
137 .map(UserBasicInfo::from);
138 Ok(member_view(&row, user))
123139 }
124140 .await;
125141 metrics::record_outcome(metrics::FEATURE_PROJECT_MEMBERS_GET, &result);
@@ -171,7 +187,10 @@
171187 None,
172188 )
173189 .await;
174 Ok(member_view(&updated))
190 let user = load_user(&svc.db, updated.user_id)
191 .await?
192 .map(UserBasicInfo::from);
193 Ok(member_view(&updated, user))
175194 }
176195 .await;
177196 metrics::record_outcome(metrics::FEATURE_PROJECT_MEMBERS_UPDATE, &result);
@@ -225,11 +244,11 @@
225244 result
226245}
227246
228fn member_view(m: &db::project::project_members::Model) -> MemberView {
247fn member_view(m: &db::project::project_members::Model, user: Option<UserBasicInfo>) -> MemberView {
229248 MemberView {
230249 id: m.id,
231250 project_id: m.project_id,
232 user_id: m.user_id,
251 user,
233252 role: m.role.into(),
234253 joined_at: m.joined_at,
235254 updated_at: m.updated_at,
Modified lib/project/features/views/usecase.rs lib/project/features/views/usecase.rs +20 −6 View lib/project/features/views/usecase.rs
@@ -1,5 +1,7 @@
11use db::project::project_views as view_entity;
22use db::project::project_views::Column as Vc;
3use db::user::users_loader::{load_user, load_users};
4use export::user::basic::UserBasicInfo;
35use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
46use time::OffsetDateTime;
57use uuid::Uuid;
@@ -54,7 +56,8 @@
5456 None,
5557 )
5658 .await;
57 Ok(view_view(&row))
59 let creator = load_user(&svc.db, actor).await?.map(UserBasicInfo::from);
60 Ok(view_view(&row, creator))
5861 }
5962 .await;
6063 metrics::record_outcome(metrics::FEATURE_PROJECT_VIEWS_CREATE, &result);
@@ -77,7 +80,12 @@
7780 .order_by_desc(Vc::CreatedAt)
7881 .all(&svc.db.reader())
7982 .await?;
80 let items = rows.iter().map(view_view).collect::<Vec<_>>();
83 let creator_ids: Vec<Uuid> = rows.iter().map(|r| r.creator_id).collect();
84 let users = load_users(&svc.db, &creator_ids).await?;
85 let items = rows
86 .iter()
87 .map(|r| view_view(r, users.get(&r.creator_id).map(UserBasicInfo::from)))
88 .collect::<Vec<_>>();
8189 let total = items.len() as u64;
8290 Ok(ViewPage { total, items })
8391 }
@@ -104,7 +112,10 @@
104112 .ok_or_else(|| {
105113 ProjectError::NotFound(format!("view {view_id} in project {project_id}"))
106114 })?;
107 Ok(view_view(&row))
115 let creator = load_user(&svc.db, row.creator_id)
116 .await?
117 .map(UserBasicInfo::from);
118 Ok(view_view(&row, creator))
108119 }
109120 .await;
110121 metrics::record_outcome(metrics::FEATURE_PROJECT_VIEWS_GET, &result);
@@ -167,7 +178,10 @@
167178 None,
168179 )
169180 .await;
170 Ok(view_view(&updated))
181 let creator = load_user(&svc.db, updated.creator_id)
182 .await?
183 .map(UserBasicInfo::from);
184 Ok(view_view(&updated, creator))
171185 }
172186 .await;
173187 metrics::record_outcome(metrics::FEATURE_PROJECT_VIEWS_UPDATE, &result);
@@ -214,11 +228,11 @@
214228 result
215229}
216230
217fn view_view(m: &view_entity::Model) -> ViewView {
231fn view_view(m: &view_entity::Model, creator: Option<UserBasicInfo>) -> ViewView {
218232 ViewView {
219233 id: m.id,
220234 project_id: m.project_id,
221 creator_id: m.creator_id,
235 creator,
222236 name: m.name.clone(),
223237 description: m.description.clone(),
224238 filter: m.filter.clone(),
Modified lib/project/handler/documents/create.rs lib/project/handler/documents/create.rs +1 −6 View lib/project/handler/documents/create.rs
@@ -27,12 +27,7 @@
2727 let space_id = space::features::lookup::lookup_space_by_name(&svc, &path.space_name).await?;
2828 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
2929 let view = svc
30 .create_document(
31 claims.sub,
32 space_id,
33 path.project_id,
34 input.into_inner(),
35 )
30 .create_document(claims.sub, space_id, path.project_id, input.into_inner())
3631 .await?;
3732 Ok(AppResponseData::ok(view).with_code(201))
3833}
Modified lib/project/handler/favorites/add.rs lib/project/handler/favorites/add.rs +1 −6 View lib/project/handler/favorites/add.rs
@@ -27,12 +27,7 @@
2727 let space_id = space::features::lookup::lookup_space_by_name(&svc, &path.space_name).await?;
2828 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
2929 let view = svc
30 .add_favorite(
31 claims.sub,
32 space_id,
33 path.project_id,
34 input.into_inner(),
35 )
30 .add_favorite(claims.sub, space_id, path.project_id, input.into_inner())
3631 .await?;
3732 Ok(AppResponseData::ok(view).with_code(201))
3833}
Modified lib/project/handler/lifecycle/update.rs lib/project/handler/lifecycle/update.rs +1 −6 View lib/project/handler/lifecycle/update.rs
@@ -27,12 +27,7 @@
2727 let space_id = space::features::lookup::lookup_space_by_name(&svc, &path.space_name).await?;
2828 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
2929 let view = svc
30 .update_project(
31 claims.sub,
32 space_id,
33 path.project_id,
34 input.into_inner(),
35 )
30 .update_project(claims.sub, space_id, path.project_id, input.into_inner())
3631 .await?;
3732 Ok(AppResponseData::ok(view))
3833}
Modified lib/project/handler/links/add.rs lib/project/handler/links/add.rs +1 −6 View lib/project/handler/links/add.rs
@@ -27,12 +27,7 @@
2727 let space_id = space::features::lookup::lookup_space_by_name(&svc, &path.space_name).await?;
2828 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
2929 let view = svc
30 .add_link(
31 claims.sub,
32 space_id,
33 path.project_id,
34 input.into_inner(),
35 )
30 .add_link(claims.sub, space_id, path.project_id, input.into_inner())
3631 .await?;
3732 Ok(AppResponseData::ok(view).with_code(201))
3833}
Modified lib/project/handler/members/add.rs lib/project/handler/members/add.rs +1 −6 View lib/project/handler/members/add.rs
@@ -27,12 +27,7 @@
2727 let space_id = space::features::lookup::lookup_space_by_name(&svc, &path.space_name).await?;
2828 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
2929 let view = svc
30 .add_member(
31 claims.sub,
32 space_id,
33 path.project_id,
34 input.into_inner(),
35 )
30 .add_member(claims.sub, space_id, path.project_id, input.into_inner())
3631 .await?;
3732 Ok(AppResponseData::ok(view).with_code(201))
3833}
Modified lib/project/handler/views/create.rs lib/project/handler/views/create.rs +1 −6 View lib/project/handler/views/create.rs
@@ -27,12 +27,7 @@
2727 let space_id = space::features::lookup::lookup_space_by_name(&svc, &path.space_name).await?;
2828 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
2929 let view = svc
30 .create_view(
31 claims.sub,
32 space_id,
33 path.project_id,
34 input.into_inner(),
35 )
30 .create_view(claims.sub, space_id, path.project_id, input.into_inner())
3631 .await?;
3732 Ok(AppResponseData::ok(view).with_code(201))
3833}
Modified lib/project/handler/openapi.rs lib/project/handler/openapi.rs +5 −3 View lib/project/handler/openapi.rs
@@ -1,15 +1,17 @@
11use utoipa::OpenApi;
22
3use crate::features::links::{AddLinkCmd, LinkKind, LinkPage, LinkView};
4use crate::features::members::{AddMemberCmd, MemberPage, MemberRole, MemberView, UpdateMemberCmd};
35use export::project::activities::{ActivityPage, ActivityView, ListActivityQuery};
4use export::project::documents::{CreateDocumentCmd, DocumentPage, DocumentView, UpdateDocumentCmd};
6use export::project::documents::{
7 CreateDocumentCmd, DocumentPage, DocumentView, UpdateDocumentCmd,
8};
59use export::project::favorites::{AddFavoriteCmd, FavoritePage, FavoriteView};
610use export::project::lifecycle::{
711 CreateProjectCmd, ListProjectQuery, ProjectHealth, ProjectPage, ProjectState, ProjectView,
812 UpdateProjectCmd,
913};
1014use export::project::views::{CreateViewCmd, UpdateViewCmd, ViewPage, ViewView};
11use crate::features::links::{AddLinkCmd, LinkKind, LinkPage, LinkView};
12use crate::features::members::{AddMemberCmd, MemberPage, MemberRole, MemberView, UpdateMemberCmd};
1315
1416#[derive(OpenApi)]
1517#[openapi(
Modified lib/repo/features/archive/usecase.rs lib/repo/features/archive/usecase.rs +1 −1 View lib/repo/features/archive/usecase.rs
@@ -1,6 +1,6 @@
11use crate::actor::RepoAccess;
22use crate::error::{RepoError, RepoResult};
3use crate::features::{authz, RepoService};
3use crate::features::{RepoService, authz};
44use crate::metrics;
55
66use super::ArchiveFormatQuery;
Modified lib/repo/features/branch/usecase.rs lib/repo/features/branch/usecase.rs +1 −1 View lib/repo/features/branch/usecase.rs
@@ -5,7 +5,7 @@
55
66use crate::actor::RepoAccess;
77use crate::error::{RepoError, RepoResult};
8use crate::features::{authz, RepoService};
8use crate::features::{RepoService, authz};
99use crate::metrics;
1010use crate::path::validate_branch_name;
1111
Modified lib/repo/features/branch_protection/usecase.rs lib/repo/features/branch_protection/usecase.rs +1 −1 View lib/repo/features/branch_protection/usecase.rs
@@ -5,7 +5,7 @@
55
66use crate::actor::RepoAccess;
77use crate::error::{RepoError, RepoResult};
8use crate::features::{authz, RepoService};
8use crate::features::{RepoService, authz};
99use crate::metrics;
1010use crate::path::validate_branch_name;
1111
Modified lib/repo/features/collab/usecase.rs lib/repo/features/collab/usecase.rs +14 −9 View lib/repo/features/collab/usecase.rs
@@ -1,17 +1,18 @@
11use db::conn::sea_orm::*;
22use db::repo::repo_collaborations as collab_entity;
33use db::repo::repo_collaborations::CollaborationMode;
4use db::user::users_loader::{load_user, load_users};
5use export::user::basic::UserBasicInfo;
46use time::OffsetDateTime;
57use uuid::Uuid;
68
79use crate::actor::RepoAccess;
810use crate::error::{RepoError, RepoResult};
9use crate::features::{authz, RepoService};
11use crate::features::{RepoService, authz};
1012use crate::metrics;
1113
1214use super::{
1315 AddCollaboratorOption, CollaboratorPage, CollaboratorView, RepoCollaboratorPermission,
14 UserSummary,
1516};
1617
1718fn parse_mode(s: &str) -> RepoResult<CollaborationMode> {
@@ -54,7 +55,15 @@
5455 .all(&db)
5556 .await?
5657 .into_iter()
57 .map(CollaboratorView::from)
58 .collect::<Vec<_>>();
59 let user_ids: Vec<Uuid> = items.iter().map(|c| c.user_id).collect();
60 let users = load_users(&svc.db, &user_ids).await?;
61 let items = items
62 .into_iter()
63 .map(|c| {
64 let user_id = c.user_id;
65 CollaboratorView::from_model(c, users.get(&user_id).map(UserBasicInfo::from))
66 })
5867 .collect();
5968 Ok(CollaboratorPage {
6069 items,
@@ -174,15 +183,11 @@
174183 Some(c) => mode_to_str(c.mode),
175184 None => "none".to_string(),
176185 };
186 let user = load_user(&svc.db, user_id).await?.map(UserBasicInfo::from);
177187 Ok(RepoCollaboratorPermission {
178188 permission,
179189 role: "collaborator".to_string(),
180 user: UserSummary {
181 id: user_id,
182 name: String::new(),
183 login: String::new(),
184 email: None,
185 },
190 user: user,
186191 })
187192 }
188193 .await;
Modified lib/repo/features/commit_status/usecase.rs lib/repo/features/commit_status/usecase.rs +1 −1 View lib/repo/features/commit_status/usecase.rs
@@ -5,7 +5,7 @@
55
66use crate::actor::RepoAccess;
77use crate::error::{RepoError, RepoResult};
8use crate::features::{authz, RepoService};
8use crate::features::{RepoService, authz};
99use crate::metrics;
1010
1111use super::types::{
Modified lib/repo/features/compare/usecase.rs lib/repo/features/compare/usecase.rs +1 −1 View lib/repo/features/compare/usecase.rs
@@ -1,6 +1,6 @@
11use crate::actor::RepoAccess;
22use crate::error::{RepoError, RepoResult};
3use crate::features::{authz, RepoService};
3use crate::features::{RepoService, authz};
44use crate::metrics;
55
66use super::CompareView;
Modified lib/repo/features/contents/usecase.rs lib/repo/features/contents/usecase.rs +2 −2 View lib/repo/features/contents/usecase.rs
@@ -2,7 +2,7 @@
22
33use crate::actor::RepoAccess;
44use crate::error::{RepoError, RepoResult};
5use crate::features::{authz, RepoService};
5use crate::features::{RepoService, authz};
66use crate::metrics;
77
88use super::{
@@ -185,4 +185,4 @@
185185 .await;
186186 metrics::record_outcome(metrics::FEATURE_CONTENTS_APPLY_DIFF_PATCH, &result);
187187 result
188}
No newline at end of file
188}
Modified lib/repo/features/deploy_key/usecase.rs lib/repo/features/deploy_key/usecase.rs +1 −1 View lib/repo/features/deploy_key/usecase.rs
@@ -5,7 +5,7 @@
55
66use crate::actor::RepoAccess;
77use crate::error::{RepoError, RepoResult};
8use crate::features::{authz, RepoService};
8use crate::features::{RepoService, authz};
99use crate::metrics;
1010
1111use super::{CreateDeployKeyOption, DeployKeyPage, DeployKeyView, ListDeployKeyQuery};
Modified lib/repo/features/extras/impls.rs lib/repo/features/extras/impls.rs +12 −3 View lib/repo/features/extras/impls.rs
@@ -1,3 +1,4 @@
1use export::user::basic::UserBasicInfo;
12use uuid::Uuid;
23
34use crate::error::RepoResult;
@@ -66,11 +67,15 @@
6667 _repo_id: Uuid,
6768 _page: Option<u64>,
6869 _limit: Option<u64>,
69 ) -> RepoResult<Vec<UserMini>> {
70 ) -> RepoResult<Vec<UserBasicInfo>> {
7071 usecase::list_stargazers(self).await
7172 }
7273
73 pub async fn list_assignees(&self, _actor: Uuid, _repo_id: Uuid) -> RepoResult<Vec<UserMini>> {
74 pub async fn list_assignees(
75 &self,
76 _actor: Uuid,
77 _repo_id: Uuid,
78 ) -> RepoResult<Vec<UserBasicInfo>> {
7479 usecase::list_assignees(self).await
7580 }
7681
@@ -83,7 +88,11 @@
8388 usecase::check_assignee(self).await
8489 }
8590
86 pub async fn list_reviewers(&self, _actor: Uuid, _repo_id: Uuid) -> RepoResult<Vec<UserMini>> {
91 pub async fn list_reviewers(
92 &self,
93 _actor: Uuid,
94 _repo_id: Uuid,
95 ) -> RepoResult<Vec<UserBasicInfo>> {
8796 usecase::list_reviewers(self).await
8897 }
8998
Modified lib/repo/features/extras/usecase.rs lib/repo/features/extras/usecase.rs +12 −13 View lib/repo/features/extras/usecase.rs
@@ -1,9 +1,11 @@
1use db::user::users_loader::load_user;
2use export::user::basic::UserBasicInfo;
13use time::OffsetDateTime;
24use uuid::Uuid;
35
46use crate::actor::RepoAccess;
57use crate::error::{RepoError, RepoResult};
6use crate::features::{authz, RepoService};
8use crate::features::{RepoService, authz};
79use crate::metrics;
810
911use super::*;
@@ -134,16 +136,16 @@
134136 result
135137}
136138
137pub async fn list_stargazers(_svc: &RepoService) -> RepoResult<Vec<UserMini>> {
139pub async fn list_stargazers(_svc: &RepoService) -> RepoResult<Vec<UserBasicInfo>> {
138140 let _timer = metrics::Timer::start(metrics::FEATURE_EXTRAS_LIST_STARGAZERS);
139 let result: RepoResult<Vec<UserMini>> = async { Ok(Vec::new()) }.await;
141 let result: RepoResult<Vec<UserBasicInfo>> = async { Ok(Vec::new()) }.await;
140142 metrics::record_outcome(metrics::FEATURE_EXTRAS_LIST_STARGAZERS, &result);
141143 result
142144}
143145
144pub async fn list_assignees(_svc: &RepoService) -> RepoResult<Vec<UserMini>> {
146pub async fn list_assignees(_svc: &RepoService) -> RepoResult<Vec<UserBasicInfo>> {
145147 let _timer = metrics::Timer::start(metrics::FEATURE_EXTRAS_LIST_ASSIGNEES);
146 let result: RepoResult<Vec<UserMini>> = async { Ok(Vec::new()) }.await;
148 let result: RepoResult<Vec<UserBasicInfo>> = async { Ok(Vec::new()) }.await;
147149 metrics::record_outcome(metrics::FEATURE_EXTRAS_LIST_ASSIGNEES, &result);
148150 result
149151}
@@ -155,9 +157,9 @@
155157 result
156158}
157159
158pub async fn list_reviewers(_svc: &RepoService) -> RepoResult<Vec<UserMini>> {
160pub async fn list_reviewers(_svc: &RepoService) -> RepoResult<Vec<UserBasicInfo>> {
159161 let _timer = metrics::Timer::start(metrics::FEATURE_EXTRAS_LIST_REVIEWERS);
160 let result: RepoResult<Vec<UserMini>> = async { Ok(Vec::new()) }.await;
162 let result: RepoResult<Vec<UserBasicInfo>> = async { Ok(Vec::new()) }.await;
161163 metrics::record_outcome(metrics::FEATURE_EXTRAS_LIST_REVIEWERS, &result);
162164 result
163165}
@@ -372,11 +374,7 @@
372374 result
373375}
374376
375pub async fn watch_repo(
376 svc: &RepoService,
377 actor: Uuid,
378 repo_id: Uuid,
379) -> RepoResult<Subscription> {
377pub async fn watch_repo(svc: &RepoService, actor: Uuid, repo_id: Uuid) -> RepoResult<Subscription> {
380378 let _timer = metrics::Timer::start(metrics::FEATURE_EXTRAS_WATCH_REPO);
381379 let result: RepoResult<Subscription> = async {
382380 let repo = authz::load_repo_by_id(svc, repo_id).await?;
@@ -384,8 +382,9 @@
384382 let now = OffsetDateTime::now_utc()
385383 .format(&time::format_description::well_known::Rfc3339)
386384 .unwrap_or_default();
385 let user = load_user(&svc.db, actor).await?.map(UserBasicInfo::from);
387386 Ok(Subscription {
388 user_id: actor,
387 user,
389388 repo_id: Uuid::nil(),
390389 mode: "watch".to_string(),
391390 created_at: now,
Modified lib/repo/features/git_ref/usecase.rs lib/repo/features/git_ref/usecase.rs +1 −1 View lib/repo/features/git_ref/usecase.rs
@@ -1,6 +1,6 @@
11use crate::actor::RepoAccess;
22use crate::error::{RepoError, RepoResult};
3use crate::features::{authz, RepoService};
3use crate::features::{RepoService, authz};
44use crate::metrics;
55
66use super::{BlobView, CommitDiffPayload, CommitView, GitRef, NoteView, TreeView};
Modified lib/repo/features/lifecycle/usecase/get.rs lib/repo/features/lifecycle/usecase/get.rs +1 −1 View lib/repo/features/lifecycle/usecase/get.rs
@@ -2,7 +2,7 @@
22
33use crate::actor::RepoAccess;
44use crate::error::RepoResult;
5use crate::features::{authz, RepoService};
5use crate::features::{RepoService, authz};
66use crate::metrics;
77
88use super::super::RepoView;
Modified lib/repo/features/lifecycle/usecase/list.rs lib/repo/features/lifecycle/usecase/list.rs +1 −1 View lib/repo/features/lifecycle/usecase/list.rs
@@ -3,7 +3,7 @@
33use uuid::Uuid;
44
55use crate::error::RepoResult;
6use crate::features::{authz, RepoService};
6use crate::features::{RepoService, authz};
77use crate::metrics;
88
99use super::super::{ListRepoQuery, RepoPage, RepoView};
Modified lib/repo/features/pull_request/usecase.rs lib/repo/features/pull_request/usecase.rs +73 −22 View lib/repo/features/pull_request/usecase.rs
@@ -10,6 +10,8 @@
1010use db::repo::repo_collaborations::CollaborationMode;
1111use db::repo::repos as repo_entity;
1212use db::user::users as user_entity;
13use db::user::users_loader::load_users;
14use export::user::basic::UserBasicInfo;
1315use gix::hash::ObjectId;
1416use gix::object::tree::diff::ChangeDetached;
1517use gix::refs::transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog};
@@ -19,7 +21,7 @@
1921
2022use crate::actor::RepoAccess;
2123use crate::error::{RepoError, RepoResult};
22use crate::features::{authz, RepoService};
24use crate::features::{RepoService, authz};
2325use crate::metrics;
2426use crate::path::{git_bare_path, validate_branch_name};
2527
@@ -65,6 +67,21 @@
6567 .map_err(Into::into)
6668}
6769
70async fn view_pr(svc: &RepoService, m: pr_entity::Model) -> RepoResult<PullRequestView> {
71 let author_id = m.author_id;
72 let merger_id = m.merger_id;
73 let mut ids = vec![author_id];
74 if let Some(mid) = merger_id {
75 ids.push(mid);
76 }
77 let users = load_users(&svc.db, &ids).await?;
78 Ok(PullRequestView::from_model(
79 m,
80 users.get(&author_id).map(UserBasicInfo::from),
81 merger_id.and_then(|mid| users.get(&mid).map(UserBasicInfo::from)),
82 ))
83}
84
6885async fn collaborator_mode(
6986 svc: &RepoService,
7087 repo_id: Uuid,
@@ -361,15 +378,29 @@
361378 }
362379
363380 let total = base.clone().count(&db).await?;
364 let items: Vec<PullRequestView> = base
381 let rows = base
365382 .clone()
366383 .order_by_desc(pr_entity::Column::AutoincrementId)
367384 .limit(Some(limit))
368385 .offset(Some(offset))
369386 .all(&db)
370 .await?
387 .await?;
388 let user_ids: Vec<Uuid> = rows
389 .iter()
390 .flat_map(|r| std::iter::once(r.author_id).chain(r.merger_id))
391 .collect();
392 let users = load_users(&svc.db, &user_ids).await?;
393 let items: Vec<PullRequestView> = rows
371394 .into_iter()
372 .map(PullRequestView::from)
395 .map(|r| {
396 let author_id = r.author_id;
397 let merger_id = r.merger_id;
398 PullRequestView::from_model(
399 r,
400 users.get(&author_id).map(UserBasicInfo::from),
401 merger_id.and_then(|mid| users.get(&mid).map(UserBasicInfo::from)),
402 )
403 })
373404 .collect();
374405
375406 Ok(PullRequestPage {
@@ -394,7 +425,7 @@
394425 authz::authorize_repo(svc, actor, &repo, RepoAccess::Read).await?;
395426 let pr = load_pr(svc, repo_id, number).await?;
396427 let pr = heal_merged(svc, repo_id, &pr).await;
397 Ok(PullRequestView::from(pr))
428 Ok(view_pr(svc, pr).await?)
398429 }
399430 .await;
400431 metrics::record_outcome(metrics::FEATURE_PULL_REQUESTS_GET, &result);
@@ -550,7 +581,7 @@
550581
551582 txn.commit().await?;
552583
553 Ok(PullRequestView::from(saved))
584 Ok(view_pr(svc, saved).await?)
554585 }
555586 .await;
556587 metrics::record_outcome(metrics::FEATURE_PULL_REQUESTS_CREATE, &result);
@@ -611,7 +642,7 @@
611642 }
612643 active.updated_at = Set(Some(OffsetDateTime::now_utc()));
613644 let saved = active.update(&db).await?;
614 Ok(PullRequestView::from(saved))
645 Ok(view_pr(svc, saved).await?)
615646 }
616647 .await;
617648 metrics::record_outcome(metrics::FEATURE_PULL_REQUESTS_UPDATE, &result);
@@ -661,7 +692,7 @@
661692
662693 insert_event(&txn, pr.id, actor, "closed", None, None).await?;
663694 txn.commit().await?;
664 Ok(PullRequestView::from(saved))
695 Ok(view_pr(svc, saved).await?)
665696 }
666697 .await;
667698 metrics::record_outcome(metrics::FEATURE_PULL_REQUESTS_CLOSE, &result);
@@ -709,7 +740,7 @@
709740
710741 insert_event(&txn, pr.id, actor, "reopened", None, None).await?;
711742 txn.commit().await?;
712 Ok(PullRequestView::from(saved))
743 Ok(view_pr(svc, saved).await?)
713744 }
714745 .await;
715746 metrics::record_outcome(metrics::FEATURE_PULL_REQUESTS_REOPEN, &result);
@@ -738,7 +769,7 @@
738769 active.is_locked = Set(true);
739770 active.updated_at = Set(Some(OffsetDateTime::now_utc()));
740771 let saved = active.update(&db).await?;
741 Ok(PullRequestView::from(saved))
772 Ok(view_pr(svc, saved).await?)
742773 }
743774 .await;
744775 metrics::record_outcome(metrics::FEATURE_PULL_REQUESTS_LOCK, &result);
@@ -767,7 +798,7 @@
767798 active.is_locked = Set(false);
768799 active.updated_at = Set(Some(OffsetDateTime::now_utc()));
769800 let saved = active.update(&db).await?;
770 Ok(PullRequestView::from(saved))
801 Ok(view_pr(svc, saved).await?)
771802 }
772803 .await;
773804 metrics::record_outcome(metrics::FEATURE_PULL_REQUESTS_UNLOCK, &result);
@@ -825,7 +856,8 @@
825856 .await?;
826857
827858 txn.commit().await?;
828 Ok(PullRequestCommentView::from(saved))
859 let user = load_user(svc, actor).await?.map(UserBasicInfo::from);
860 Ok(PullRequestCommentView::from_model(saved, user))
829861 }
830862 .await;
831863 metrics::record_outcome(metrics::FEATURE_PULL_REQUESTS_CREATE_COMMENT, &result);
@@ -844,13 +876,19 @@
844876 authz::authorize_repo(svc, actor, &repo, RepoAccess::Read).await?;
845877 let pr = load_pr(svc, repo_id, number).await?;
846878 let db = svc.db.reader();
847 let items: Vec<PullRequestCommentView> = comment_entity::Entity::find()
879 let rows = comment_entity::Entity::find()
848880 .filter(comment_entity::Column::PullRequestId.eq(pr.id))
849881 .order_by_asc(comment_entity::Column::CreatedAt)
850882 .all(&db)
851 .await?
883 .await?;
884 let user_ids: Vec<Uuid> = rows.iter().map(|c| c.user_id).collect();
885 let users = load_users(&svc.db, &user_ids).await?;
886 let items: Vec<PullRequestCommentView> = rows
852887 .into_iter()
853 .map(PullRequestCommentView::from)
888 .map(|c| {
889 let user_id = c.user_id;
890 PullRequestCommentView::from_model(c, users.get(&user_id).map(UserBasicInfo::from))
891 })
854892 .collect();
855893 Ok(items)
856894 }
@@ -899,7 +937,8 @@
899937 .await?;
900938
901939 txn.commit().await?;
902 Ok(PullRequestReviewView::from(saved))
940 let user = load_user(svc, actor).await?.map(UserBasicInfo::from);
941 Ok(PullRequestReviewView::from_model(saved, user))
903942 }
904943 .await;
905944 metrics::record_outcome(metrics::FEATURE_PULL_REQUESTS_SUBMIT_REVIEW, &result);
@@ -918,13 +957,19 @@
918957 authz::authorize_repo(svc, actor, &repo, RepoAccess::Read).await?;
919958 let pr = load_pr(svc, repo_id, number).await?;
920959 let db = svc.db.reader();
921 let items: Vec<PullRequestReviewView> = review_entity::Entity::find()
960 let rows = review_entity::Entity::find()
922961 .filter(review_entity::Column::PullRequestId.eq(pr.id))
923962 .order_by_asc(review_entity::Column::CreatedAt)
924963 .all(&db)
925 .await?
964 .await?;
965 let user_ids: Vec<Uuid> = rows.iter().map(|c| c.user_id).collect();
966 let users = load_users(&svc.db, &user_ids).await?;
967 let items: Vec<PullRequestReviewView> = rows
926968 .into_iter()
927 .map(PullRequestReviewView::from)
969 .map(|c| {
970 let user_id = c.user_id;
971 PullRequestReviewView::from_model(c, users.get(&user_id).map(UserBasicInfo::from))
972 })
928973 .collect();
929974 Ok(items)
930975 }
@@ -945,13 +990,19 @@
945990 authz::authorize_repo(svc, actor, &repo, RepoAccess::Read).await?;
946991 let pr = load_pr(svc, repo_id, number).await?;
947992 let db = svc.db.reader();
948 let items: Vec<PullRequestEventView> = event_entity::Entity::find()
993 let rows = event_entity::Entity::find()
949994 .filter(event_entity::Column::PullRequestId.eq(pr.id))
950995 .order_by_asc(event_entity::Column::CreatedAt)
951996 .all(&db)
952 .await?
997 .await?;
998 let user_ids: Vec<Uuid> = rows.iter().map(|c| c.user_id).collect();
999 let users = load_users(&svc.db, &user_ids).await?;
1000 let items: Vec<PullRequestEventView> = rows
9531001 .into_iter()
954 .map(PullRequestEventView::from)
1002 .map(|c| {
1003 let user_id = c.user_id;
1004 PullRequestEventView::from_model(c, users.get(&user_id).map(UserBasicInfo::from))
1005 })
9551006 .collect();
9561007 Ok(items)
9571008 }
Modified lib/repo/features/release/usecase.rs lib/repo/features/release/usecase.rs +32 −7 View lib/repo/features/release/usecase.rs
@@ -1,11 +1,13 @@
11use db::conn::sea_orm::*;
22use db::repo::repo_release as release_entity;
3use db::user::users_loader::{load_user, load_users};
4use export::user::basic::UserBasicInfo;
35use time::OffsetDateTime;
46use uuid::Uuid;
57
68use crate::actor::RepoAccess;
79use crate::error::{RepoError, RepoResult};
8use crate::features::{authz, RepoService};
10use crate::features::{RepoService, authz};
911use crate::metrics;
1012use crate::path::validate_tag_name;
1113
@@ -58,7 +60,15 @@
5860 .all(&db)
5961 .await?
6062 .into_iter()
61 .map(ReleaseView::from)
63 .collect::<Vec<_>>();
64 let user_ids: Vec<Uuid> = items.iter().map(|r| r.publisher_id).collect();
65 let users = load_users(&svc.db, &user_ids).await?;
66 let items = items
67 .into_iter()
68 .map(|r| {
69 let publisher_id = r.publisher_id;
70 ReleaseView::from_model(r, users.get(&publisher_id).map(UserBasicInfo::from))
71 })
6272 .collect();
6373 Ok(ReleasePage {
6474 items,
@@ -81,7 +91,10 @@
8191 let repo = authz::load_repo_by_id(svc, repo_id).await?;
8292 authz::authorize_repo(svc, actor, &repo, RepoAccess::Read).await?;
8393 let release = load_release(svc, repo_id, release_id).await?;
84 Ok(ReleaseView::from(release))
94 let publisher = load_user(&svc.db, release.publisher_id)
95 .await?
96 .map(UserBasicInfo::from);
97 Ok(ReleaseView::from_model(release, publisher))
8598 }
8699 .await;
87100 metrics::record_outcome(metrics::FEATURE_RELEASES_GET, &result);
@@ -106,7 +119,10 @@
106119 .one(&db)
107120 .await?
108121 .ok_or_else(|| RepoError::NotFound("no published release".into()))?;
109 Ok(ReleaseView::from(release))
122 let publisher = load_user(&svc.db, release.publisher_id)
123 .await?
124 .map(UserBasicInfo::from);
125 Ok(ReleaseView::from_model(release, publisher))
110126 }
111127 .await;
112128 metrics::record_outcome(metrics::FEATURE_RELEASES_GET_LATEST, &result);
@@ -133,7 +149,10 @@
133149 if release.is_tag {
134150 return Err(RepoError::NotFound(format!("release for tag {tag}")));
135151 }
136 Ok(ReleaseView::from(release))
152 let publisher = load_user(&svc.db, release.publisher_id)
153 .await?
154 .map(UserBasicInfo::from);
155 Ok(ReleaseView::from_model(release, publisher))
137156 }
138157 .await;
139158 metrics::record_outcome(metrics::FEATURE_RELEASES_GET_BY_TAG, &result);
@@ -181,7 +200,10 @@
181200 updated_at: Set(now),
182201 };
183202 let saved = active.insert(&db).await?;
184 Ok(ReleaseView::from(saved))
203 let publisher = load_user(&svc.db, saved.publisher_id)
204 .await?
205 .map(UserBasicInfo::from);
206 Ok(ReleaseView::from_model(saved, publisher))
185207 }
186208 .await;
187209 metrics::record_outcome(metrics::FEATURE_RELEASES_CREATE, &result);
@@ -232,7 +254,10 @@
232254 }
233255 active.updated_at = Set(OffsetDateTime::now_utc());
234256 let saved = active.update(&db).await?;
235 Ok(ReleaseView::from(saved))
257 let publisher = load_user(&svc.db, saved.publisher_id)
258 .await?
259 .map(UserBasicInfo::from);
260 Ok(ReleaseView::from_model(saved, publisher))
236261 }
237262 .await;
238263 metrics::record_outcome(metrics::FEATURE_RELEASES_EDIT, &result);
Modified lib/repo/features/subscriber/usecase.rs lib/repo/features/subscriber/usecase.rs +12 −4 View lib/repo/features/subscriber/usecase.rs
@@ -1,9 +1,11 @@
11use db::conn::sea_orm::*;
22use db::repo::repo_watch as watch_entity;
3use db::user::users_loader::load_users;
4use export::user::basic::UserBasicInfo;
35
46use crate::actor::RepoAccess;
57use crate::error::RepoResult;
6use crate::features::{authz, RepoService};
8use crate::features::{RepoService, authz};
79use crate::metrics;
810
911use super::{ListSubscriberQuery, SubscriberPage, SubscriberView};
@@ -24,13 +26,19 @@
2426 let db = svc.db.reader();
2527 let query = watch_entity::Entity::find().filter(watch_entity::Column::RepoId.eq(repo_id));
2628 let total = query.clone().count(&db).await?;
27 let items = query
29 let items: Vec<watch_entity::Model> = query
2830 .limit(Some(limit))
2931 .offset(Some(offset))
3032 .all(&db)
31 .await?
33 .await?;
34 let user_ids: Vec<uuid::Uuid> = items.iter().map(|w| w.user_id).collect();
35 let users = load_users(&svc.db, &user_ids).await?;
36 let items = items
3237 .into_iter()
33 .map(SubscriberView::from)
38 .map(|w| {
39 let user_id = w.user_id;
40 SubscriberView::from_model(w, users.get(&user_id).map(UserBasicInfo::from))
41 })
3442 .collect();
3543 Ok(SubscriberPage {
3644 items,
Modified lib/repo/features/sync/usecase.rs lib/repo/features/sync/usecase.rs +1 −1 View lib/repo/features/sync/usecase.rs
@@ -6,7 +6,7 @@
66
77use crate::actor::RepoAccess;
88use crate::error::{RepoError, RepoResult};
9use crate::features::{authz, RepoService};
9use crate::features::{RepoService, authz};
1010use crate::metrics;
1111
1212use super::{SyncResult, SyncStatus};
Modified lib/repo/features/tag/usecase.rs lib/repo/features/tag/usecase.rs +1 −1 View lib/repo/features/tag/usecase.rs
@@ -6,7 +6,7 @@
66
77use crate::actor::RepoAccess;
88use crate::error::{RepoError, RepoResult};
9use crate::features::{authz, RepoService};
9use crate::features::{RepoService, authz};
1010use crate::metrics;
1111use crate::path::validate_tag_name;
1212
Modified lib/repo/features/tag_protection/usecase.rs lib/repo/features/tag_protection/usecase.rs +1 −1 View lib/repo/features/tag_protection/usecase.rs
@@ -5,7 +5,7 @@
55
66use crate::actor::RepoAccess;
77use crate::error::{RepoError, RepoResult};
8use crate::features::{authz, RepoService};
8use crate::features::{RepoService, authz};
99use crate::metrics;
1010
1111use super::{
Modified lib/repo/features/topic/usecase.rs lib/repo/features/topic/usecase.rs +5 −8 View lib/repo/features/topic/usecase.rs
@@ -5,7 +5,7 @@
55
66use crate::actor::RepoAccess;
77use crate::error::{RepoError, RepoResult};
8use crate::features::{authz, RepoService};
8use crate::features::{RepoService, authz};
99use crate::metrics;
1010use crate::path::{normalize_topic, sanitize_topics};
1111
@@ -199,8 +199,7 @@
199199 let result: RepoResult<TopicSearchResult> = async {
200200 let db = svc.db.reader();
201201 let visible_topic_ids: std::collections::HashSet<Uuid> = {
202 let (visible_channels, collab_repos) =
203 authz::visible_repo_scope(svc, actor).await?;
202 let (visible_channels, collab_repos) = authz::visible_repo_scope(svc, actor).await?;
204203 let mut link_query = db::repo::repo_relation_topic::Entity::find();
205204 let mut cond = Condition::any();
206205 if !visible_channels.is_empty() {
@@ -218,9 +217,7 @@
218217 }
219218 }
220219 if !collab_repos.is_empty() {
221 cond = cond.add(
222 db::repo::repo_relation_topic::Column::RepoId.is_in(collab_repos),
223 );
220 cond = cond.add(db::repo::repo_relation_topic::Column::RepoId.is_in(collab_repos));
224221 }
225222 if cond.is_empty() {
226223 return Ok(TopicSearchResult { topics: vec![] });
@@ -239,8 +236,8 @@
239236 let limit = q.limit.unwrap_or(svc.config.default_pagination);
240237 let offset = q.page.unwrap_or(0).saturating_mul(limit);
241238 let limit = std::cmp::min(limit, svc.config.max_pagination);
242 let mut query = topic_entity::Entity::find()
243 .filter(topic_entity::Column::Id.is_in(visible_topic_ids));
239 let mut query =
240 topic_entity::Entity::find().filter(topic_entity::Column::Id.is_in(visible_topic_ids));
244241 if let Some(s) = q.q.as_deref() {
245242 if !s.is_empty() {
246243 query = query.filter(topic_entity::Column::Name.contains(s));
Modified lib/repo/features/authz.rs lib/repo/features/authz.rs +8 −11 View lib/repo/features/authz.rs
@@ -128,9 +128,7 @@
128128 permission::PermissionError::Insufficient => {
129129 RepoError::Forbidden("insufficient permissions".into())
130130 }
131 permission::PermissionError::Forbidden => {
132 RepoError::Forbidden("access forbidden".into())
133 }
131 permission::PermissionError::Forbidden => RepoError::Forbidden("access forbidden".into()),
134132 }
135133}
136134
@@ -274,10 +272,7 @@
274272 fn rec(pat: &[char], txt: &[char]) -> bool {
275273 match (pat.first(), txt.first()) {
276274 (None, None) => true,
277 (Some('*'), _) => {
278 rec(&pat[1..], txt)
279 || (!txt.is_empty() && rec(pat, &txt[1..]))
280 }
275 (Some('*'), _) => rec(&pat[1..], txt) || (!txt.is_empty() && rec(pat, &txt[1..])),
281276 (Some('?'), Some(_)) => rec(&pat[1..], &txt[1..]),
282277 (Some(a), Some(b)) if a == b => rec(&pat[1..], &txt[1..]),
283278 _ => false,
@@ -307,7 +302,9 @@
307302 .filter(policy_entity::Column::BranchPattern.is_not_null())
308303 .all(db)
309304 .await?;
310 Ok(pattern_rows
311 .into_iter()
312 .find(|m| m.branch_pattern.as_deref().is_some_and(|p| glob_match(p, branch_name))))
313}
No newline at end of file
305 Ok(pattern_rows.into_iter().find(|m| {
306 m.branch_pattern
307 .as_deref()
308 .is_some_and(|p| glob_match(p, branch_name))
309 }))
310}
Modified lib/repo/features/mod.rs lib/repo/features/mod.rs +1 −1 View lib/repo/features/mod.rs
@@ -1,7 +1,7 @@
11pub use crate::service::RepoService;
22
3pub mod archive;
34pub mod authz;
4pub mod archive;
55pub mod branch;
66pub mod branch_protection;
77pub mod collab;
Modified lib/repo/graphql/mod.rs lib/repo/graphql/mod.rs +1 −3 View lib/repo/graphql/mod.rs
@@ -1,6 +1,4 @@
1use async_graphql::{
2 ComplexObject, Context, EmptyMutation, EmptySubscription, ID, Object, Schema,
3};
1use async_graphql::{ComplexObject, Context, EmptyMutation, EmptySubscription, ID, Object, Schema};
42use serde_json::json;
53use uuid::Uuid;
64
Modified lib/repo/handler/branch/mod.rs lib/repo/handler/branch/mod.rs +1 −0 View lib/repo/handler/branch/mod.rs
@@ -4,4 +4,5 @@
44pub(crate) mod list;
55pub(crate) mod merge_upstream;
66pub(crate) mod rename;
7pub(crate) mod set_default;
78pub(crate) mod update;
Modified lib/repo/handler/extras/reviews.rs lib/repo/handler/extras/reviews.rs +3 −3 View lib/repo/handler/extras/reviews.rs
@@ -6,7 +6,7 @@
66
77use crate::error::RepoResult;
88use crate::features::RepoService;
9use crate::features::extras::UserMini;
9use export::user::basic::UserBasicInfo;
1010
1111#[utoipa::path(
1212 get,
@@ -16,14 +16,14 @@
1616 ("space_name" = String, Path),
1717 ("repo_id" = Uuid, Path),
1818 ),
19 responses((status = 200, description = "List reviewers", body = AppResponseData<Vec<UserMini>>)),
19 responses((status = 200, description = "List reviewers", body = AppResponseData<Vec<UserBasicInfo>>)),
2020 security(("bearer_auth" = []))
2121)]
2222pub(crate) async fn handler(
2323 State(svc): State<RepoService>,
2424 AuthUser { claims }: AuthUser,
2525 Path((space_name, repo_id)): Path<(String, Uuid)>,
26) -> RepoResult<AppResponseData<Vec<UserMini>>> {
26) -> RepoResult<AppResponseData<Vec<UserBasicInfo>>> {
2727 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2828 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
2929 let _channel_id = crate::features::lookup::channel_for_repo(&svc, repo_id).await?;
Modified lib/repo/handler/extras/stargazer.rs lib/repo/handler/extras/stargazer.rs +3 −3 View lib/repo/handler/extras/stargazer.rs
@@ -6,7 +6,7 @@
66
77use crate::error::RepoResult;
88use crate::features::RepoService;
9use crate::features::extras::UserMini;
9use export::user::basic::UserBasicInfo;
1010
1111#[derive(serde::Deserialize, utoipa::IntoParams)]
1212#[into_params(parameter_in = Query)]
@@ -24,7 +24,7 @@
2424 ("space_name" = String, Path),
2525 ("repo_id" = Uuid, Path),
2626 ),
27 responses((status = 200, description = "List stargazers", body = AppResponseData<Vec<UserMini>>)),
27 responses((status = 200, description = "List stargazers", body = AppResponseData<Vec<UserBasicInfo>>)),
2828 security(("bearer_auth" = []))
2929)]
3030pub(crate) async fn handler(
@@ -32,7 +32,7 @@
3232 AuthUser { claims }: AuthUser,
3333 Path((space_name, repo_id)): Path<(String, Uuid)>,
3434 Query(q): Query<StargazerQuery>,
35) -> RepoResult<AppResponseData<Vec<UserMini>>> {
35) -> RepoResult<AppResponseData<Vec<UserBasicInfo>>> {
3636 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
3737 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
3838 let _channel_id = crate::features::lookup::channel_for_repo(&svc, repo_id).await?;
Modified lib/repo/handler/extras/subscription.rs lib/repo/handler/extras/subscription.rs +6 −1 View lib/repo/handler/extras/subscription.rs
@@ -1,5 +1,7 @@
11use axum::extract::{Path, State};
2use db::user::users_loader::load_user;
23use export::data::{AppResponseData, NoData};
4use export::user::basic::UserBasicInfo;
35
46use jwt::extractor::AuthUser;
57use uuid::Uuid;
@@ -27,8 +29,11 @@
2729 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2830 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
2931 let _channel_id = crate::features::lookup::channel_for_repo(&svc, repo_id).await?;
32 let user = load_user(&svc.db, claims.sub)
33 .await?
34 .map(UserBasicInfo::from);
3035 Ok(AppResponseData::ok(Subscription {
31 user_id: claims.sub,
36 user,
3237 repo_id,
3338 mode: "watch".to_string(),
3439 created_at: String::new(),
Modified lib/repo/handler/lifecycle/mod.rs lib/repo/handler/lifecycle/mod.rs +3 −0 View lib/repo/handler/lifecycle/mod.rs
@@ -1,10 +1,13 @@
11pub(crate) mod archive;
22pub(crate) mod create;
33pub(crate) mod delete;
4pub(crate) mod features;
45pub(crate) mod fork;
56pub(crate) mod generate;
67pub(crate) mod get;
78pub(crate) mod list;
9pub(crate) mod merge_strategies;
10pub(crate) mod readme;
811pub(crate) mod rename;
912pub(crate) mod transfer;
1013pub(crate) mod update;
Modified lib/repo/handler/pull_request/mod.rs lib/repo/handler/pull_request/mod.rs +3 −0 View lib/repo/handler/pull_request/mod.rs
@@ -1,8 +1,11 @@
11pub(crate) mod close;
22pub(crate) mod comment;
3pub(crate) mod commits;
34pub(crate) mod create;
45pub(crate) mod event;
6pub(crate) mod files;
57pub(crate) mod get;
8pub(crate) mod labels;
69pub(crate) mod list;
710pub(crate) mod lock;
811pub(crate) mod merge;
Modified lib/repo/handler/release/mod.rs lib/repo/handler/release/mod.rs +1 −0 View lib/repo/handler/release/mod.rs
@@ -6,3 +6,4 @@
66pub(crate) mod get;
77pub(crate) mod latest;
88pub(crate) mod list;
9pub(crate) mod upload_asset;
Modified lib/repo/handler/mod.rs lib/repo/handler/mod.rs +1 −0 View lib/repo/handler/mod.rs
@@ -22,5 +22,6 @@
2222pub mod tag;
2323pub mod tag_protection;
2424pub mod topic;
25pub mod tree;
2526
2627pub use routes::router;
Modified lib/repo/handler/openapi.rs lib/repo/handler/openapi.rs +14 −2 View lib/repo/handler/openapi.rs
@@ -45,6 +45,7 @@
4545 crate::handler::branch::delete::handler,
4646 crate::handler::branch::rename::handler,
4747 crate::handler::branch::merge_upstream::handler,
48 crate::handler::branch::set_default::handler,
4849
4950 crate::handler::pull_request::list::handler,
5051 crate::handler::pull_request::get::handler,
@@ -60,6 +61,11 @@
6061 crate::handler::pull_request::review::list::handler,
6162 crate::handler::pull_request::review::submit::handler,
6263 crate::handler::pull_request::event::handler,
64 crate::handler::pull_request::files::handler,
65 crate::handler::pull_request::commits::handler,
66 crate::handler::pull_request::labels::list_handler,
67 crate::handler::pull_request::labels::add_handler,
68 crate::handler::pull_request::labels::remove_handler,
6369
6470 crate::handler::branch_protection::list::handler,
6571 crate::handler::branch_protection::create::handler,
@@ -87,6 +93,7 @@
8793 crate::handler::release::edit::handler,
8894 crate::handler::release::delete::handler,
8995 crate::handler::release::delete_by_tag::handler,
96 crate::handler::release::upload_asset::handler,
9097
9198 crate::handler::collab::list::handler,
9299 crate::handler::collab::is::handler,
@@ -166,6 +173,13 @@
166173 crate::handler::extras::subscription::get_handler,
167174 crate::handler::extras::subscription::put_handler,
168175 crate::handler::extras::subscription::delete_handler,
176
177 crate::handler::tree::handler,
178 crate::handler::lifecycle::readme::handler,
179 crate::handler::lifecycle::features::get_handler,
180 crate::handler::lifecycle::features::patch_handler,
181 crate::handler::lifecycle::merge_strategies::get_handler,
182 crate::handler::lifecycle::merge_strategies::patch_handler,
169183 ),
170184 components(schemas(
171185
@@ -235,14 +249,12 @@
235249 release::EditReleaseOption,
236250 release::ListReleaseQuery,
237251 release::ReleasePage,
238 release::PublisherSummary,
239252
240253 collab::CollaboratorView,
241254 collab::AddCollaboratorOption,
242255 collab::CollaboratorPage,
243256 collab::ListCollaboratorQuery,
244257 collab::RepoCollaboratorPermission,
245 collab::UserSummary,
246258
247259 deploy_key::DeployKeyView,
248260 deploy_key::CreateDeployKeyOption,
Modified lib/repo/handler/routes.rs lib/repo/handler/routes.rs +47 −1 View lib/repo/handler/routes.rs
@@ -83,6 +83,23 @@
8383 get(super::pull_request::event::handler),
8484 )
8585 .route(
86 "/repos/{space_name}/{repo_id}/pulls/{number}/files",
87 get(super::pull_request::files::handler),
88 )
89 .route(
90 "/repos/{space_name}/{repo_id}/pulls/{number}/commits",
91 get(super::pull_request::commits::handler),
92 )
93 .route(
94 "/repos/{space_name}/{repo_id}/pulls/{number}/labels",
95 get(super::pull_request::labels::list_handler),
96 )
97 .route(
98 "/repos/{space_name}/{repo_id}/pulls/{number}/labels/{label_id}",
99 post(super::pull_request::labels::add_handler)
100 .delete(super::pull_request::labels::remove_handler),
101 )
102 .route(
86103 "/repos/{space_name}/{repo_id}/branches/{name}",
87104 get(super::branch::get::handler)
88105 .put(super::branch::update::handler)
@@ -93,6 +110,10 @@
93110 patch(super::branch::rename::handler),
94111 )
95112 .route(
113 "/repos/{space_name}/{repo_id}/branches/{branch_name}/set-default",
114 post(super::branch::set_default::handler),
115 )
116 .route(
96117 "/repos/{space_name}/{repo_id}/merge-upstream",
97118 post(super::branch::merge_upstream::handler),
98119 )
@@ -258,8 +279,11 @@
258279 post(super::sync::run::handler),
259280 )
260281 .route("/repos/search", get(super::extras::search::handler))
261 .route("/repositories/{id}", get(super::extras::repo_by_id::handler))
262282 .route(
283 "/repositories/{id}",
284 get(super::extras::repo_by_id::handler),
285 )
286 .route(
263287 "/repos/{space_name}/{repo_id}/avatar",
264288 post(super::extras::avatar::post_handler).delete(super::extras::avatar::delete_handler),
265289 )
@@ -358,6 +382,28 @@
358382 "/repos/{space_name}/{repo_id}/releases/{release_id}/assets/{attachment_id}",
359383 get(super::extras::release_attachment::get_handler),
360384 )
385 .route(
386 "/repos/{space_name}/{repo_id}/releases/{release_id}/assets/upload",
387 post(super::release::upload_asset::handler),
388 )
389 .route(
390 "/repos/{space_name}/{repo_id}/tree",
391 get(super::tree::handler),
392 )
393 .route(
394 "/repos/{space_name}/{repo_id}/readme",
395 get(super::lifecycle::readme::handler),
396 )
397 .route(
398 "/repos/{space_name}/{repo_id}/features",
399 get(super::lifecycle::features::get_handler)
400 .patch(super::lifecycle::features::patch_handler),
401 )
402 .route(
403 "/repos/{space_name}/{repo_id}/merge-strategies",
404 get(super::lifecycle::merge_strategies::get_handler)
405 .patch(super::lifecycle::merge_strategies::patch_handler),
406 )
361407 .layer(from_fn_with_state(
362408 svc.clone(),
363409 inject_jwt_ctx::<RepoService>,
Modified lib/repo/tests/pull_request/close.rs lib/repo/tests/pull_request/close.rs +1 −1 View lib/repo/tests/pull_request/close.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/comment_create.rs lib/repo/tests/pull_request/comment_create.rs +1 −1 View lib/repo/tests/pull_request/comment_create.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/comment_list.rs lib/repo/tests/pull_request/comment_list.rs +1 −1 View lib/repo/tests/pull_request/comment_list.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/create.rs lib/repo/tests/pull_request/create.rs +1 −1 View lib/repo/tests/pull_request/create.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/event.rs lib/repo/tests/pull_request/event.rs +1 −1 View lib/repo/tests/pull_request/event.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/get.rs lib/repo/tests/pull_request/get.rs +1 −1 View lib/repo/tests/pull_request/get.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/list.rs lib/repo/tests/pull_request/list.rs +1 −1 View lib/repo/tests/pull_request/list.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/lock.rs lib/repo/tests/pull_request/lock.rs +1 −1 View lib/repo/tests/pull_request/lock.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/main.rs lib/repo/tests/pull_request/main.rs +1 −1 View lib/repo/tests/pull_request/main.rs
@@ -37,8 +37,8 @@
3737use http::{Request, StatusCode};
3838use jwt::JwtState;
3939use jwt::keys::SigningKeyCache;
40use repo::RepoService;
4041use repo::handler::router as repo_router;
41use repo::RepoService;
4242use tokio::sync::RwLock;
4343use tower::util::ServiceExt;
4444
Modified lib/repo/tests/pull_request/merge.rs lib/repo/tests/pull_request/merge.rs +1 −1 View lib/repo/tests/pull_request/merge.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/reopen.rs lib/repo/tests/pull_request/reopen.rs +1 −1 View lib/repo/tests/pull_request/reopen.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/review_list.rs lib/repo/tests/pull_request/review_list.rs +1 −1 View lib/repo/tests/pull_request/review_list.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/review_submit.rs lib/repo/tests/pull_request/review_submit.rs +1 −1 View lib/repo/tests/pull_request/review_submit.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/unlock.rs lib/repo/tests/pull_request/unlock.rs +1 −1 View lib/repo/tests/pull_request/unlock.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/tests/pull_request/update.rs lib/repo/tests/pull_request/update.rs +1 −1 View lib/repo/tests/pull_request/update.rs
@@ -1,4 +1,4 @@
1use super::{SPACE_NAME, REPO_ID, expect_unauthorized};
1use super::{REPO_ID, SPACE_NAME, expect_unauthorized};
22
33const BASE: &str = "/repos";
44
Modified lib/repo/actor.rs lib/repo/actor.rs +1 −1 View lib/repo/actor.rs
@@ -46,7 +46,7 @@
4646 pub scopes: HashSet<TokenScope>,
4747 pub is_site_admin: bool,
4848
49 pub credential_max: Option<RepoAccess>,
49 pub credential_max: Option<RepoAccess>,
5050
5151 pub channel_role: Option<ChannelRole>,
5252