Skip to content
Clone

feat(tag-protection): implement CRUD operations for tag protection policies

ZhenYi committed
parent 06505aa commit d9ee0dd Browse files

44 files changed

+720 −567

This change is very large, so some diffs are not shown. Clone the repository to see the full change.

File tree 44
Modified lib/git/src/handler/html/blame.rs View lib/git/src/handler/html/blame.rs
@@ -7,11 +7,9 @@
77
88use crate::handler::html::error::html_err;
99use crate::handler::html::{
10 HtmlRepoPath, archived_class, resolve, response_html, short_sha, validate_path,
11};
10 HtmlRepoPath, archived_class, resolve_view, response_html, short_sha, };
1211use crate::service::GitService;
1312use crate::share::access::BasicAuth;
14use crate::share::validate::validate_revision;
1513
1614#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1715pub struct BlameQuery {
@@ -39,26 +37,13 @@
3937 Query(q): Query<BlameQuery>,
4038 auth: BasicAuth,
4139) -> Response {
42 let ctx = match resolve(service.clone(), path, auth).await {
43 Ok(c) => c,
40 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, true).await {
41 Ok(v) => v,
4442 Err(resp) => return resp,
4543 };
46 let revision = q
47 .revision
48 .as_deref()
49 .unwrap_or(ctx.dto.default_branch.as_str())
50 .to_string();
51 if validate_revision(&revision).is_err() {
52 return html_err(404, "ref invalid");
53 }
54 let raw_path = q.path.unwrap_or_default();
55 let clean_path = match validate_path(&raw_path) {
56 Some(p) => p,
57 None => return html_err(404, "path invalid"),
58 };
59 if clean_path.is_empty() {
60 return html_err(404, "path required");
61 }
44 let ctx = view.ctx;
45 let revision = view.revision;
46 let clean_path = view.clean_path;
6247
6348 let blame_ranges = match service
6449 .blame(
Modified lib/git/src/handler/html/blob_content.rs View lib/git/src/handler/html/blob_content.rs
@@ -5,11 +5,9 @@
55
66use crate::handler::html::error::html_err;
77use crate::handler::html::{
8 HtmlRepoPath, archived_class, format_size, resolve, response_html, validate_path,
9};
8 HtmlRepoPath, archived_class, format_size, resolve_view, response_html, };
109use crate::service::GitService;
1110use crate::share::access::BasicAuth;
12use crate::share::validate::validate_revision;
1311
1412#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1513pub struct BlobContentQuery {
@@ -37,26 +35,13 @@
3735 Query(q): Query<BlobContentQuery>,
3836 auth: BasicAuth,
3937) -> Response {
40 let ctx = match resolve(service.clone(), path, auth).await {
41 Ok(c) => c,
38 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, true).await {
39 Ok(v) => v,
4240 Err(resp) => return resp,
4341 };
44 let revision = q
45 .revision
46 .as_deref()
47 .unwrap_or(ctx.dto.default_branch.as_str())
48 .to_string();
49 if validate_revision(&revision).is_err() {
50 return html_err(404, "ref invalid");
51 }
52 let raw_path = q.path.unwrap_or_default();
53 let clean_path = match validate_path(&raw_path) {
54 Some(p) => p,
55 None => return html_err(404, "path invalid"),
56 };
57 if clean_path.is_empty() {
58 return html_err(404, "path required");
59 }
42 let ctx = view.ctx;
43 let revision = view.revision;
44 let clean_path = view.clean_path;
6045
6146 let baby = service.baby(&ctx.ctx);
6247 let entry = match baby
Modified lib/git/src/handler/html/blob_header.rs View lib/git/src/handler/html/blob_header.rs
@@ -2,11 +2,10 @@
22use axum::response::Response;
33use maud::html;
44
5use crate::handler::html::error::html_err;
6use crate::handler::html::{HtmlRepoPath, archived_class, resolve, response_html};
5
6use crate::handler::html::{HtmlRepoPath, archived_class, resolve_view, response_html};
77use crate::service::GitService;
88use crate::share::access::BasicAuth;
9use crate::share::validate::validate_revision;
109
1110#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1211pub struct BlobHeaderQuery {
@@ -34,26 +33,13 @@
3433 Query(q): Query<BlobHeaderQuery>,
3534 auth: BasicAuth,
3635) -> Response {
37 let ctx = match resolve(service, path, auth).await {
38 Ok(c) => c,
36 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, true).await {
37 Ok(v) => v,
3938 Err(resp) => return resp,
4039 };
41 let revision = q
42 .revision
43 .as_deref()
44 .unwrap_or(ctx.dto.default_branch.as_str())
45 .to_string();
46 if validate_revision(&revision).is_err() {
47 return html_err(404, "ref invalid");
48 }
49 let raw_path = q.path.unwrap_or_default();
50 let clean_path = match crate::handler::html::validate_path(&raw_path) {
51 Some(p) => p,
52 None => return html_err(404, "path invalid"),
53 };
54 if clean_path.is_empty() {
55 return html_err(404, "path required");
56 }
40 let ctx = view.ctx;
41 let revision = view.revision;
42 let clean_path = view.clean_path;
5743 let base =
5844 "git-blob-header flex items-center justify-between border-b border-zinc-200 px-3 py-2";
5945 let cls = archived_class(&ctx.dto, base);
Modified lib/git/src/handler/html/commits.rs View lib/git/src/handler/html/commits.rs
@@ -7,12 +7,10 @@
77use crate::handler::html::error::html_err;
88use crate::handler::html::{
99 HtmlRepoPath, archived_class, clamp_limit, first_line, iso_time_unix, relative_time_unix,
10 resolve, response_html, short_sha, validate_path,
11};
10 resolve_view, response_html, short_sha, };
1211use crate::service::GitService;
1312use crate::service::commits::ListCommitsCmd;
1413use crate::share::access::BasicAuth;
15use crate::share::validate::validate_revision;
1614
1715#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1816pub struct CommitsQuery {
@@ -44,23 +42,13 @@
4442 Query(q): Query<CommitsQuery>,
4543 auth: BasicAuth,
4644) -> Response {
47 let ctx = match resolve(service.clone(), path, auth).await {
48 Ok(c) => c,
45 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, false).await {
46 Ok(v) => v,
4947 Err(resp) => return resp,
5048 };
51 let revision = q
52 .revision
53 .as_deref()
54 .unwrap_or(ctx.dto.default_branch.as_str())
55 .to_string();
56 if validate_revision(&revision).is_err() {
57 return html_err(404, "ref invalid");
58 }
59 let raw_path = q.path.unwrap_or_default();
60 let clean_path = match validate_path(&raw_path) {
61 Some(p) => p,
62 None => return html_err(404, "path invalid"),
63 };
49 let ctx = view.ctx;
50 let revision = view.revision;
51 let clean_path = view.clean_path;
6452 let limit = clamp_limit(q.limit).min(100);
6553
6654 let cmd = ListCommitsCmd {
Modified lib/git/src/handler/html/mod.rs View lib/git/src/handler/html/mod.rs
@@ -167,6 +167,40 @@
167167 })
168168}
169169
170pub struct HtmlView {
171 pub ctx: HtmlCtx,
172 pub revision: String,
173 pub clean_path: String,
174}
175
176pub async fn resolve_view(
177 service: GitService,
178 path: HtmlRepoPath,
179 auth: BasicAuth,
180 revision: Option<String>,
181 raw_path: Option<String>,
182 require_path: bool,
183) -> Result<HtmlView, Response> {
184 let ctx = resolve(service, path, auth).await?;
185 let revision = revision
186 .as_deref()
187 .unwrap_or(ctx.dto.default_branch.as_str())
188 .to_string();
189 if crate::share::validate::validate_revision(&revision).is_err() {
190 return Err(html_err(404, "ref invalid"));
191 }
192 let clean_path = validate_path(raw_path.as_deref().unwrap_or_default())
193 .ok_or_else(|| html_err(404, "path invalid"))?;
194 if require_path && clean_path.is_empty() {
195 return Err(html_err(404, "path required"));
196 }
197 Ok(HtmlView {
198 ctx,
199 revision,
200 clean_path,
201 })
202}
203
170204pub fn clamp_limit(q: Option<usize>) -> usize {
171205 match q {
172206 None => DEFAULT_LIMIT,
Modified lib/git/src/handler/html/readme.rs View lib/git/src/handler/html/readme.rs
@@ -5,11 +5,9 @@
55
66use crate::handler::html::error::html_err;
77use crate::handler::html::{
8 HtmlRepoPath, archived_class, format_size, resolve, response_html, validate_path,
9};
8 HtmlRepoPath, archived_class, format_size, resolve_view, response_html, };
109use crate::service::GitService;
1110use crate::share::access::BasicAuth;
12use crate::share::validate::validate_revision;
1311
1412const README_CANDIDATES: &[&str] = &[
1513 "README.md",
@@ -50,23 +48,13 @@
5048 Query(q): Query<ReadmeQuery>,
5149 auth: BasicAuth,
5250) -> Response {
53 let ctx = match resolve(service.clone(), path, auth).await {
54 Ok(c) => c,
51 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, false).await {
52 Ok(v) => v,
5553 Err(resp) => return resp,
5654 };
57 let revision = q
58 .revision
59 .as_deref()
60 .unwrap_or(ctx.dto.default_branch.as_str())
61 .to_string();
62 if validate_revision(&revision).is_err() {
63 return html_err(404, "ref invalid");
64 }
65 let raw_path = q.path.unwrap_or_default();
66 let clean_path = match validate_path(&raw_path) {
67 Some(p) => p,
68 None => return html_err(404, "path invalid"),
69 };
55 let ctx = view.ctx;
56 let revision = view.revision;
57 let clean_path = view.clean_path;
7058
7159 let baby = service.baby(&ctx.ctx);
7260 let dir_arg = if clean_path.is_empty() {
Modified lib/git/src/handler/html/tree.rs View lib/git/src/handler/html/tree.rs
@@ -7,12 +7,10 @@
77
88use crate::handler::html::error::html_err;
99use crate::handler::html::{
10 HtmlRepoPath, archived_class, clamp_limit, first_line, iso_time, relative_time, resolve,
11 response_html, short_sha, validate_path,
12};
10 HtmlRepoPath, archived_class, clamp_limit, first_line, iso_time, relative_time, resolve_view,
11 response_html, short_sha, };
1312use crate::service::GitService;
1413use crate::share::access::BasicAuth;
15use crate::share::validate::validate_revision;
1614
1715#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1816pub struct TreeQuery {
@@ -42,23 +40,13 @@
4240 Query(q): Query<TreeQuery>,
4341 auth: BasicAuth,
4442) -> Response {
45 let ctx = match resolve(service, path, auth).await {
46 Ok(c) => c,
43 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, false).await {
44 Ok(v) => v,
4745 Err(resp) => return resp,
48 };
49 let revision = q
50 .revision
51 .as_deref()
52 .unwrap_or(ctx.dto.default_branch.as_str())
53 .to_string();
54 if validate_revision(&revision).is_err() {
55 return html_err(404, "ref invalid");
56 }
57 let raw_path = q.path.unwrap_or_default();
58 let clean_path = match validate_path(&raw_path) {
59 Some(p) => p,
60 None => return html_err(404, "path invalid"),
6146 };
47 let ctx = view.ctx;
48 let revision = view.revision;
49 let clean_path = view.clean_path;
6250 let limit = clamp_limit(q.limit);
6351 let rows = match ctx
6452 .service
Modified lib/git/src/handler/html/tree_row.rs View lib/git/src/handler/html/tree_row.rs
@@ -5,12 +5,12 @@
55
66use crate::handler::html::error::html_err;
77use crate::handler::html::{
8 HtmlRepoPath, first_line, iso_time, relative_time, resolve, response_html, short_sha,
8 HtmlRepoPath, first_line, iso_time, relative_time, resolve_view, response_html, short_sha,
99};
1010use crate::service::GitService;
1111use crate::share::access::BasicAuth;
12use crate::share::validate::validate_revision;
1312
13
1414#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1515pub struct TreeRowQuery {
1616 #[serde(default)]
@@ -39,23 +39,13 @@
3939 Query(q): Query<TreeRowQuery>,
4040 auth: BasicAuth,
4141) -> Response {
42 let ctx = match resolve(service, path, auth).await {
43 Ok(c) => c,
42 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, false).await {
43 Ok(v) => v,
4444 Err(resp) => return resp,
4545 };
46 let revision = q
47 .revision
48 .as_deref()
49 .unwrap_or(ctx.dto.default_branch.as_str())
50 .to_string();
51 if validate_revision(&revision).is_err() {
52 return html_err(404, "ref invalid");
53 }
54 let parent = q.path.unwrap_or_default();
55 let parent_clean = match crate::handler::html::validate_path(&parent) {
56 Some(p) => p,
57 None => return html_err(404, "path invalid"),
58 };
46 let ctx = view.ctx;
47 let revision = view.revision;
48 let parent_clean = view.clean_path;
5949 let name = match q.name.as_deref() {
6050 Some(n) if !n.is_empty() && n != ".." && !n.contains('/') && !n.contains('\\') => {
6151 n.to_string()
Modified lib/git/src/handler/lfs/mod.rs View lib/git/src/handler/lfs/mod.rs
@@ -1,6 +1,31 @@
1use session::Session;
2use uuid::Uuid;
3
4use crate::errors::ApiError;
5use crate::handler::{require_repo_admin, require_writer};
6use crate::service::GitService;
7
18pub mod cleanup_unreferenced;
29pub mod list_locks;
310pub mod lock;
411pub mod unlock;
512pub mod unlock_by_id;
613pub mod verify_locks;
14
15pub(crate) async fn unlock_actor(
16 service: &GitService,
17 session: &Session,
18 space_name: String,
19 repo_name: String,
20 force: bool,
21) -> Result<Uuid, ApiError> {
22 if force {
23 Ok(
24 require_repo_admin(service, session, space_name, repo_name)
25 .await?
26 .1,
27 )
28 } else {
29 Ok(require_writer(service, session, space_name, repo_name).await?.1)
30 }
31}
Modified lib/git/src/handler/lfs/unlock.rs View lib/git/src/handler/lfs/unlock.rs
@@ -3,7 +3,8 @@
33use session::Session;
44
55use crate::errors::{ApiError, GitApiError};
6use crate::handler::{RepoPath, require_repo_admin, require_writer};
6use crate::handler::RepoPath;
7use crate::handler::lfs::unlock_actor;
78use crate::service::GitService;
89
910#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
@@ -30,25 +31,14 @@
3031 Query(q): Query<LfsUnlockPathQuery>,
3132) -> Result<AppResponseData<NoData>, ApiError> {
3233 let force = q.force.unwrap_or(false);
33 let uid = if force {
34 require_repo_admin(
35 &service,
36 &session,
37 path.space_name.clone(),
38 path.repo_name.clone(),
39 )
40 .await?
41 .1
42 } else {
43 let (_ctx, uid) = require_writer(
44 &service,
45 &session,
46 path.space_name.clone(),
47 path.repo_name.clone(),
48 )
49 .await?;
50 uid
51 };
34 let uid = unlock_actor(
35 &service,
36 &session,
37 path.space_name.clone(),
38 path.repo_name.clone(),
39 force,
40 )
41 .await?;
5242 service
5343 .lfs_unlock(path.space_name, path.repo_name, uid, q.path, force)
5444 .await
Modified lib/git/src/handler/lfs/unlock_by_id.rs View lib/git/src/handler/lfs/unlock_by_id.rs
@@ -3,7 +3,7 @@
33use session::Session;
44
55use crate::errors::{ApiError, GitApiError};
6use crate::handler::{require_repo_admin, require_writer};
6use crate::handler::lfs::unlock_actor;
77use crate::service::GitService;
88
99#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
@@ -36,25 +36,14 @@
3636 Query(q): Query<LfsUnlockQuery>,
3737) -> Result<AppResponseData<NoData>, ApiError> {
3838 let force = q.force.unwrap_or(false);
39 let uid = if force {
40 require_repo_admin(
41 &service,
42 &session,
43 path.space_name.clone(),
44 path.repo_name.clone(),
45 )
46 .await?
47 .1
48 } else {
49 let (_ctx, uid) = require_writer(
50 &service,
51 &session,
52 path.space_name.clone(),
53 path.repo_name.clone(),
54 )
55 .await?;
56 uid
57 };
39 let uid = unlock_actor(
40 &service,
41 &session,
42 path.space_name.clone(),
43 path.repo_name.clone(),
44 force,
45 )
46 .await?;
5847 service
5948 .lfs_unlock_by_id(path.space_name, path.repo_name, uid, path.lock_id, force)
6049 .await
Added lib/git/src/handler/tag_protection/create_policy.rs View lib/git/src/handler/tag_protection/create_policy.rs
@@ -0,0 +1,41 @@
1use axum::Json;
2use axum::extract::{Path, State};
3use export::rest::AppResponseData;
4use session::Session;
5
6use crate::errors::{ApiError, GitApiError};
7use crate::handler::RepoPath;
8use crate::handler::require_repo_admin;
9use crate::service::GitService;
10use crate::service::protection::{CreateTagPolicyCmd, TagPolicyDto};
11
12#[utoipa::path(
13 post,
14 path = "/repos/{space_name}/{repo_name}/tag-protection",
15 tag = "tag_protection",
16 description = "为标签名模式创建保护策略;模式命中且创建者不在白名单内的 tag 推送将被拒绝。",
17 params(RepoPath),
18 request_body = CreateTagPolicyCmd,
19 responses(
20 (status = 200, description = "created tag policy", body = AppResponseData<TagPolicyDto>)
21 )
22)]
23pub async fn create_policy(
24 State(service): State<GitService>,
25 session: Session,
26 Path(path): Path<RepoPath>,
27 Json(cmd): Json<CreateTagPolicyCmd>,
28) -> Result<AppResponseData<TagPolicyDto>, ApiError> {
29 let _ = require_repo_admin(
30 &service,
31 &session,
32 path.space_name.clone(),
33 path.repo_name.clone(),
34 )
35 .await?;
36 let out = service
37 .create_tag_policy(path.space_name, path.repo_name, cmd)
38 .await
39 .map_err(GitApiError::from)?;
40 Ok(AppResponseData::ok(out))
41}
Added lib/git/src/handler/tag_protection/delete_policy.rs View lib/git/src/handler/tag_protection/delete_policy.rs
@@ -0,0 +1,38 @@
1use axum::extract::{Path, State};
2use axum::http::StatusCode;
3use export::rest::{AppResponseData, NoData};
4use session::Session;
5
6use crate::errors::{ApiError, GitApiError};
7use crate::handler::branch_protection::PolicyPath;
8use crate::handler::require_repo_admin;
9use crate::service::GitService;
10
11#[utoipa::path(
12 delete,
13 path = "/repos/{space_name}/{repo_name}/tag-protection/{policy_id}",
14 tag = "tag_protection",
15 description = "删除标签保护策略,并清理该策略下的用户白名单。",
16 params(PolicyPath),
17 responses(
18 (status = 200, description = "tag policy deleted", body = AppResponseData<NoData>)
19 )
20)]
21pub async fn delete_policy(
22 State(service): State<GitService>,
23 session: Session,
24 Path(path): Path<PolicyPath>,
25) -> Result<(StatusCode, AppResponseData<NoData>), ApiError> {
26 let _ = require_repo_admin(
27 &service,
28 &session,
29 path.space_name.clone(),
30 path.repo_name.clone(),
31 )
32 .await?;
33 service
34 .delete_tag_policy(path.space_name, path.repo_name, path.policy_id)
35 .await
36 .map_err(GitApiError::from)?;
37 Ok((StatusCode::OK, AppResponseData::ok(NoData)))
38}
Added lib/git/src/handler/tag_protection/get_policy.rs View lib/git/src/handler/tag_protection/get_policy.rs
@@ -0,0 +1,38 @@
1use axum::extract::{Path, State};
2use export::rest::AppResponseData;
3use session::Session;
4
5use crate::errors::{ApiError, GitApiError};
6use crate::handler::branch_protection::PolicyPath;
7use crate::handler::require_repo_admin;
8use crate::service::GitService;
9use crate::service::protection::TagPolicyDto;
10
11#[utoipa::path(
12 get,
13 path = "/repos/{space_name}/{repo_name}/tag-protection/{policy_id}",
14 tag = "tag_protection",
15 description = "读取单条标签保护策略。",
16 params(PolicyPath),
17 responses(
18 (status = 200, description = "tag protection policy", body = AppResponseData<TagPolicyDto>)
19 )
20)]
21pub async fn get_policy(
22 State(service): State<GitService>,
23 session: Session,
24 Path(path): Path<PolicyPath>,
25) -> Result<AppResponseData<TagPolicyDto>, ApiError> {
26 let _ = require_repo_admin(
27 &service,
28 &session,
29 path.space_name.clone(),
30 path.repo_name.clone(),
31 )
32 .await?;
33 let out = service
34 .get_tag_policy(path.space_name, path.repo_name, path.policy_id)
35 .await
36 .map_err(GitApiError::from)?;
37 Ok(AppResponseData::ok(out))
38}
Added lib/git/src/handler/tag_protection/list_policies.rs View lib/git/src/handler/tag_protection/list_policies.rs
@@ -0,0 +1,38 @@
1use axum::extract::{Path, State};
2use export::rest::AppResponseData;
3use session::Session;
4
5use crate::errors::{ApiError, GitApiError};
6use crate::handler::RepoPath;
7use crate::handler::require_repo_admin;
8use crate::service::GitService;
9use crate::service::protection::TagPolicyDto;
10
11#[utoipa::path(
12 get,
13 path = "/repos/{space_name}/{repo_name}/tag-protection",
14 tag = "tag_protection",
15 description = "列出标签保护策略。",
16 params(RepoPath),
17 responses(
18 (status = 200, description = "tag protection policies", body = AppResponseData<Vec<TagPolicyDto>>)
19 )
20)]
21pub async fn list_policies(
22 State(service): State<GitService>,
23 session: Session,
24 Path(path): Path<RepoPath>,
25) -> Result<AppResponseData<Vec<TagPolicyDto>>, ApiError> {
26 let _ = require_repo_admin(
27 &service,
28 &session,
29 path.space_name.clone(),
30 path.repo_name.clone(),
31 )
32 .await?;
33 let out = service
34 .list_tag_policies(path.space_name, path.repo_name)
35 .await
36 .map_err(GitApiError::from)?;
37 Ok(AppResponseData::ok(out))
38}
Added lib/git/src/handler/tag_protection/mod.rs View lib/git/src/handler/tag_protection/mod.rs
@@ -0,0 +1,5 @@
1pub mod create_policy;
2pub mod delete_policy;
3pub mod get_policy;
4pub mod list_policies;
5pub mod update_policy;
Added lib/git/src/handler/tag_protection/update_policy.rs View lib/git/src/handler/tag_protection/update_policy.rs
@@ -0,0 +1,41 @@
1use axum::Json;
2use axum::extract::{Path, State};
3use export::rest::AppResponseData;
4use session::Session;
5
6use crate::errors::{ApiError, GitApiError};
7use crate::handler::branch_protection::PolicyPath;
8use crate::handler::require_repo_admin;
9use crate::service::GitService;
10use crate::service::protection::{TagPolicyDto, UpdateTagPolicyCmd};
11
12#[utoipa::path(
13 patch,
14 path = "/repos/{space_name}/{repo_name}/tag-protection/{policy_id}",
15 tag = "tag_protection",
16 description = "更新标签保护策略的模式。",
17 params(PolicyPath),
18 request_body = UpdateTagPolicyCmd,
19 responses(
20 (status = 200, description = "updated tag policy", body = AppResponseData<TagPolicyDto>)
21 )
22)]
23pub async fn update_policy(
24 State(service): State<GitService>,
25 session: Session,
26 Path(path): Path<PolicyPath>,
27 Json(cmd): Json<UpdateTagPolicyCmd>,
28) -> Result<AppResponseData<TagPolicyDto>, ApiError> {
29 let _ = require_repo_admin(
30 &service,
31 &session,
32 path.space_name.clone(),
33 path.repo_name.clone(),
34 )
35 .await?;
36 let out = service
37 .update_tag_policy(path.space_name, path.repo_name, path.policy_id, cmd)
38 .await
39 .map_err(GitApiError::from)?;
40 Ok(AppResponseData::ok(out))
41}
Modified lib/git/src/handler/mod.rs View lib/git/src/handler/mod.rs
@@ -25,6 +25,7 @@
2525pub mod signatures;
2626pub mod stars;
2727pub mod submodules;
28pub mod tag_protection;
2829pub mod tags;
2930pub mod topics;
3031pub mod trees;
Modified lib/git/src/service/protection.rs View lib/git/src/service/protection.rs
@@ -1,5 +1,7 @@
11use globset::Glob;
22use models::repo::repo_branch_protection_policy;
3use models::repo::repo_tag_protection_policy;
4use models::repo::repo_tag_protection_whitelists;
35use sea_orm::{
46 ActiveModelTrait, ActiveValue::Set, ColumnTrait, EntityTrait, QueryFilter, QueryOrder,
57};
@@ -59,9 +61,8 @@
5961 }
6062}
6163
62#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
63pub struct CreateBranchPolicyCmd {
64 pub branch_pattern: String,
64#[derive(Debug, Clone, Deserialize, Default, utoipa::ToSchema)]
65pub struct BranchPolicyFlags {
6566 #[serde(default)]
6667 pub enable_push: Option<bool>,
6768 #[serde(default)]
@@ -91,35 +92,18 @@
9192}
9293
9394#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
95pub struct CreateBranchPolicyCmd {
96 pub branch_pattern: String,
97 #[serde(flatten)]
98 pub flags: BranchPolicyFlags,
99}
100
101#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
94102pub struct UpdateBranchPolicyCmd {
95103 #[serde(default)]
96104 pub branch_pattern: Option<String>,
97 #[serde(default)]
98 pub enable_push: Option<bool>,
99 #[serde(default)]
100 pub enable_push_whitelist: Option<bool>,
101 #[serde(default)]
102 pub enable_merge_whitelist: Option<bool>,
103 #[serde(default)]
104 pub required_approvals: Option<i32>,
105 #[serde(default)]
106 pub enable_approvals_whitelist: Option<bool>,
107 #[serde(default)]
108 pub block_on_rejected_reviews: Option<bool>,
109 #[serde(default)]
110 pub block_on_change_requested_reviews: Option<bool>,
111 #[serde(default)]
112 pub block_on_outdated_branch: Option<bool>,
113 #[serde(default)]
114 pub dismiss_stale_approvals: Option<bool>,
115 #[serde(default)]
116 pub require_signed_commits: Option<bool>,
117 #[serde(default)]
118 pub protected_file_patterns: Option<String>,
119 #[serde(default)]
120 pub unprotected_file_patterns: Option<String>,
121 #[serde(default)]
122 pub enable_force_push: Option<bool>,
105 #[serde(flatten)]
106 pub flags: BranchPolicyFlags,
123107}
124108
125109fn validate_pattern(pattern: &str) -> Result<(), GitError> {
@@ -193,10 +177,10 @@
193177 ) -> Result<BranchPolicyDto, GitError> {
194178 let ctx = self.context(space_name, repo_name).await?;
195179 validate_pattern(&cmd.branch_pattern)?;
196 let required = cmd.required_approvals.unwrap_or(0);
180 let required = cmd.flags.required_approvals.unwrap_or(0);
197181 validate_approvals(required)?;
198 let protected = cmd.protected_file_patterns.unwrap_or_default();
199 let unprotected = cmd.unprotected_file_patterns.unwrap_or_default();
182 let protected = cmd.flags.protected_file_patterns.unwrap_or_default();
183 let unprotected = cmd.flags.unprotected_file_patterns.unwrap_or_default();
200184 validate_patterns(&protected)?;
201185 validate_patterns(&unprotected)?;
202186 let now = OffsetDateTime::now_utc();
@@ -204,22 +188,20 @@
204188 id: Set(Uuid::now_v7()),
205189 repo_id: Set(ctx.model.id),
206190 branch_pattern: Set(cmd.branch_pattern.trim().to_string()),
207 enable_push: Set(cmd.enable_push.unwrap_or(true)),
208 enable_push_whitelist: Set(cmd.enable_push_whitelist.unwrap_or(false)),
191 enable_push: Set(cmd.flags.enable_push.unwrap_or(true)),
192 enable_push_whitelist: Set(cmd.flags.enable_push_whitelist.unwrap_or(false)),
209193 push_whitelist_deploy_keys: Set(false),
210 enable_merge_whitelist: Set(cmd.enable_merge_whitelist.unwrap_or(false)),
194 enable_merge_whitelist: Set(cmd.flags.enable_merge_whitelist.unwrap_or(false)),
211195 required_approvals: Set(required),
212 enable_approvals_whitelist: Set(cmd.enable_approvals_whitelist.unwrap_or(false)),
213 block_on_rejected_reviews: Set(cmd.block_on_rejected_reviews.unwrap_or(true)),
214 block_on_change_requested_reviews: Set(cmd
215 .block_on_change_requested_reviews
216 .unwrap_or(true)),
217 block_on_outdated_branch: Set(cmd.block_on_outdated_branch.unwrap_or(false)),
218 dismiss_stale_approvals: Set(cmd.dismiss_stale_approvals.unwrap_or(true)),
219 require_signed_commits: Set(cmd.require_signed_commits.unwrap_or(false)),
196 enable_approvals_whitelist: Set(cmd.flags.enable_approvals_whitelist.unwrap_or(false)),
197 block_on_rejected_reviews: Set(cmd.flags.block_on_rejected_reviews.unwrap_or(true)),
198 block_on_change_requested_reviews: Set(cmd.flags.block_on_change_requested_reviews.unwrap_or(true)),
199 block_on_outdated_branch: Set(cmd.flags.block_on_outdated_branch.unwrap_or(false)),
200 dismiss_stale_approvals: Set(cmd.flags.dismiss_stale_approvals.unwrap_or(true)),
201 require_signed_commits: Set(cmd.flags.require_signed_commits.unwrap_or(false)),
220202 protected_file_patterns: Set(protected),
221203 unprotected_file_patterns: Set(unprotected),
222 enable_force_push: Set(cmd.enable_force_push.unwrap_or(false)),
204 enable_force_push: Set(cmd.flags.enable_force_push.unwrap_or(false)),
223205 created_at: Set(now),
224206 updated_at: Set(now),
225207 ..Default::default()
@@ -254,50 +236,50 @@
254236 validate_pattern(pattern)?;
255237 model.branch_pattern = Set(pattern.trim().to_string());
256238 }
257 if let Some(count) = cmd.required_approvals {
239 if let Some(count) = cmd.flags.required_approvals {
258240 validate_approvals(count)?;
259241 model.required_approvals = Set(count);
260242 }
261 if let Some(raw) = cmd.protected_file_patterns.as_deref() {
243 if let Some(raw) = cmd.flags.protected_file_patterns.as_deref() {
262244 validate_patterns(raw)?;
263245 model.protected_file_patterns = Set(raw.to_string());
264246 }
265 if let Some(raw) = cmd.unprotected_file_patterns.as_deref() {
247 if let Some(raw) = cmd.flags.unprotected_file_patterns.as_deref() {
266248 validate_patterns(raw)?;
267249 model.unprotected_file_patterns = Set(raw.to_string());
268250 }
269251 for (flag, value) in [
270 (&cmd.enable_push, &mut model.enable_push),
271 (&cmd.enable_push_whitelist, &mut model.enable_push_whitelist),
252 (&cmd.flags.enable_push, &mut model.enable_push),
253 (&cmd.flags.enable_push_whitelist, &mut model.enable_push_whitelist),
272254 (
273 &cmd.enable_merge_whitelist,
255 &cmd.flags.enable_merge_whitelist,
274256 &mut model.enable_merge_whitelist,
275257 ),
276258 (
277 &cmd.enable_approvals_whitelist,
259 &cmd.flags.enable_approvals_whitelist,
278260 &mut model.enable_approvals_whitelist,
279261 ),
280262 (
281 &cmd.block_on_rejected_reviews,
263 &cmd.flags.block_on_rejected_reviews,
282264 &mut model.block_on_rejected_reviews,
283265 ),
284266 (
285 &cmd.block_on_change_requested_reviews,
267 &cmd.flags.block_on_change_requested_reviews,
286268 &mut model.block_on_change_requested_reviews,
287269 ),
288270 (
289 &cmd.block_on_outdated_branch,
271 &cmd.flags.block_on_outdated_branch,
290272 &mut model.block_on_outdated_branch,
291273 ),
292274 (
293 &cmd.dismiss_stale_approvals,
275 &cmd.flags.dismiss_stale_approvals,
294276 &mut model.dismiss_stale_approvals,
295277 ),
296278 (
297 &cmd.require_signed_commits,
279 &cmd.flags.require_signed_commits,
298280 &mut model.require_signed_commits,
299281 ),
300 (&cmd.enable_force_push, &mut model.enable_force_push),
282 (&cmd.flags.enable_force_push, &mut model.enable_force_push),
301283 ] {
302284 if let Some(value_flag) = flag {
303285 *value = Set(*value_flag);
@@ -334,3 +316,160 @@
334316 Ok(())
335317 }
336318}
319
320#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
321pub struct TagPolicyDto {
322 pub id: Uuid,
323 pub repo_id: Uuid,
324 pub name_pattern: String,
325 pub created_at: OffsetDateTime,
326 pub updated_at: OffsetDateTime,
327}
328
329impl From<repo_tag_protection_policy::Model> for TagPolicyDto {
330 fn from(m: repo_tag_protection_policy::Model) -> Self {
331 Self {
332 id: m.id,
333 repo_id: m.repo_id,
334 name_pattern: m.name_pattern,
335 created_at: m.created_at,
336 updated_at: m.updated_at,
337 }
338 }
339}
340
341#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
342pub struct CreateTagPolicyCmd {
343 pub name_pattern: String,
344}
345
346#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
347pub struct UpdateTagPolicyCmd {
348 #[serde(default)]
349 pub name_pattern: Option<String>,
350}
351
352fn validate_tag_pattern(pattern: &str) -> Result<(), GitError> {
353 let trimmed = pattern.trim();
354 if trimmed.is_empty() || trimmed.len() > 255 || trimmed.chars().any(|c| c.is_control()) {
355 return Err(GitError::InvalidArgument(
356 "name_pattern must be 1-255 printable characters".to_string(),
357 ));
358 }
359 Glob::new(trimmed)
360 .map_err(|e| GitError::InvalidArgument(format!("invalid tag pattern: {e}")))?;
361 Ok(())
362}
363
364impl GitService {
365 pub async fn list_tag_policies(
366 &self,
367 space_name: String,
368 repo_name: String,
369 ) -> Result<Vec<TagPolicyDto>, GitError> {
370 let ctx = self.context(space_name, repo_name).await?;
371 let rows = repo_tag_protection_policy::Entity::find()
372 .filter(repo_tag_protection_policy::Column::RepoId.eq(ctx.model.id))
373 .order_by_asc(repo_tag_protection_policy::Column::NamePattern)
374 .all(self.db_read())
375 .await?;
376 Ok(rows.into_iter().map(TagPolicyDto::from).collect())
377 }
378
379 pub async fn get_tag_policy(
380 &self,
381 space_name: String,
382 repo_name: String,
383 policy_id: Uuid,
384 ) -> Result<TagPolicyDto, GitError> {
385 let ctx = self.context(space_name, repo_name).await?;
386 let row = repo_tag_protection_policy::Entity::find_by_id(policy_id)
387 .filter(repo_tag_protection_policy::Column::RepoId.eq(ctx.model.id))
388 .one(self.db_read())
389 .await?
390 .ok_or(GitError::NotFound)?;
391 Ok(TagPolicyDto::from(row))
392 }
393
394 pub async fn create_tag_policy(
395 &self,
396 space_name: String,
397 repo_name: String,
398 cmd: CreateTagPolicyCmd,
399 ) -> Result<TagPolicyDto, GitError> {
400 let ctx = self.context(space_name, repo_name).await?;
401 validate_tag_pattern(&cmd.name_pattern)?;
402 let now = OffsetDateTime::now_utc();
403 let row = repo_tag_protection_policy::ActiveModel {
404 id: Set(Uuid::now_v7()),
405 repo_id: Set(ctx.model.id),
406 name_pattern: Set(cmd.name_pattern.trim().to_string()),
407 created_at: Set(now),
408 updated_at: Set(now),
409 ..Default::default()
410 };
411 let created = match row.insert(self.db_write()).await {
412 Ok(created) => created,
413 Err(error) if is_unique_violation(&error) => {
414 return Err(GitError::Conflict(
415 "a tag protection policy for this pattern already exists".to_string(),
416 ));
417 }
418 Err(error) => return Err(error.into()),
419 };
420 Ok(TagPolicyDto::from(created))
421 }
422
423 pub async fn update_tag_policy(
424 &self,
425 space_name: String,
426 repo_name: String,
427 policy_id: Uuid,
428 cmd: UpdateTagPolicyCmd,
429 ) -> Result<TagPolicyDto, GitError> {
430 let ctx = self.context(space_name, repo_name).await?;
431 let row = repo_tag_protection_policy::Entity::find_by_id(policy_id)
432 .filter(repo_tag_protection_policy::Column::RepoId.eq(ctx.model.id))
433 .one(self.db_read())
434 .await?
435 .ok_or(GitError::NotFound)?;
436 let mut model: repo_tag_protection_policy::ActiveModel = row.into();
437 if let Some(pattern) = cmd.name_pattern.as_deref() {
438 validate_tag_pattern(pattern)?;
439 model.name_pattern = Set(pattern.trim().to_string());
440 }
441 model.updated_at = Set(OffsetDateTime::now_utc());
442 let updated = match model.update(self.db_write()).await {
443 Ok(updated) => updated,
444 Err(error) if is_unique_violation(&error) => {
445 return Err(GitError::Conflict(
446 "a tag protection policy for this pattern already exists".to_string(),
447 ));
448 }
449 Err(error) => return Err(error.into()),
450 };
451 Ok(TagPolicyDto::from(updated))
452 }
453
454 pub async fn delete_tag_policy(
455 &self,
456 space_name: String,
457 repo_name: String,
458 policy_id: Uuid,
459 ) -> Result<(), GitError> {
460 let ctx = self.context(space_name, repo_name).await?;
461 repo_tag_protection_whitelists::Entity::delete_many()
462 .filter(repo_tag_protection_whitelists::Column::PolicyId.eq(policy_id))
463 .exec(self.db_write())
464 .await?;
465 let deleted = repo_tag_protection_policy::Entity::delete_many()
466 .filter(repo_tag_protection_policy::Column::Id.eq(policy_id))
467 .filter(repo_tag_protection_policy::Column::RepoId.eq(ctx.model.id))
468 .exec(self.db_write())
469 .await?;
470 if deleted.rows_affected == 0 {
471 return Err(GitError::NotFound);
472 }
473 Ok(())
474 }
475}
Modified lib/git/src/smart/dispatch.rs View lib/git/src/smart/dispatch.rs
@@ -17,7 +17,7 @@
1717use crate::smart::pre_receive::{self, parse_receive_pack_refs};
1818use crate::smart::rpc::{self, GitService};
1919use crate::smart::service::SmartService;
20use crate::sync::post_receive::enqueue_post_receive_push;
20use crate::sync::post_receive::enqueue_post_receive_push_logged;
2121
2222const MAX_RPC_BODY: usize = 512 * 1024 * 1024;
2323
@@ -188,7 +188,7 @@
188188 if is_receive
189189 && let Some(queue) = queue
190190 {
191 match enqueue_post_receive_push(
191 enqueue_post_receive_push_logged(
192192 &queue,
193193 &settings,
194194 space_id,
@@ -196,19 +196,7 @@
196196 actor_id,
197197 &bare,
198198 )
199 .await
200 {
201 Ok(n) if n > 0 => {
202 tracing::debug!(
203 count = n,
204 space_id = %space_id,
205 repo_id = %repo_id,
206 "enqueued post-receive ref updates"
207 )
208 }
209 Ok(_) => tracing::debug!(space_id = %space_id, repo_id = %repo_id, "no refs to enqueue"),
210 Err(e) => tracing::warn!(error = %e, space_id = %space_id, repo_id = %repo_id, "post-receive enqueue failed"),
211 }
199 .await;
212200 }
213201 Ok::<_, axum::Error>(Bytes::new())
214202 }))
Modified lib/git/src/ssh/handler/channel.rs View lib/git/src/ssh/handler/channel.rs
@@ -13,7 +13,7 @@
1313use crate::ssh::error::SshError;
1414use crate::ssh::git_service::apply_hardened_env;
1515use crate::ssh::handler::{ChannelState, GitSshHandler};
16use crate::sync::post_receive::enqueue_post_receive_push;
16use crate::sync::post_receive::enqueue_post_receive_push_logged;
1717
1818pub const MAX_SSH_CHANNEL_BUF: usize = 64 * 1024 * 1024;
1919pub const MAX_GIT_OUTPUT_BUF: usize = 256 * 1024 * 1024;
@@ -296,7 +296,7 @@
296296 && let (Some(repo_id), Some(space_id), Some(queue)) =
297297 (repo_id, space_id, &queue)
298298 {
299 match enqueue_post_receive_push(
299 enqueue_post_receive_push_logged(
300300 queue,
301301 &sync_settings,
302302 space_id,
@@ -304,28 +304,7 @@
304304 actor_id,
305305 &bare,
306306 )
307 .await
308 {
309 Ok(n) if n > 0 => {
310 tracing::debug!(
311 count = n,
312 space_id = %space_id,
313 repo_id = %repo_id,
314 "enqueued post-receive ref updates"
315 )
316 }
317 Ok(_) => tracing::debug!(
318 space_id = %space_id,
319 repo_id = %repo_id,
320 "no refs to enqueue"
321 ),
322 Err(e) => tracing::warn!(
323 error = %e,
324 space_id = %space_id,
325 repo_id = %repo_id,
326 "post-receive enqueue failed"
327 ),
328 }
307 .await;
329308 }
330309 let code = if result.is_ok() { 0 } else { 1 };
331310 tracing::debug!(
Modified lib/git/src/sync/post_receive.rs View lib/git/src/sync/post_receive.rs
@@ -151,4 +151,31 @@
151151 Ok(task.refs.len())
152152}
153153
154pub async fn enqueue_post_receive_push_logged(
155 queue: &SyncQueue,
156 settings: &SyncSettings,
157 space_id: Uuid,
158 repo_id: Uuid,
159 actor_id: Option<Uuid>,
160 bare: &Path,
161) {
162 match enqueue_post_receive_push(queue, settings, space_id, repo_id, actor_id, bare).await {
163 Ok(n) if n > 0 => {
164 tracing::debug!(
165 count = n,
166 space_id = %space_id,
167 repo_id = %repo_id,
168 "enqueued post-receive ref updates"
169 )
170 }
171 Ok(_) => tracing::debug!(space_id = %space_id, repo_id = %repo_id, "no refs to enqueue"),
172 Err(e) => tracing::warn!(
173 error = %e,
174 space_id = %space_id,
175 repo_id = %repo_id,
176 "post-receive enqueue failed"
177 ),
178 }
179}
180
154181pub use crate::sync::event::EnqueueError;
Modified lib/git/src/openapi.rs View lib/git/src/openapi.rs
@@ -30,6 +30,11 @@
3030 handler::branch_protection::get_policy::get_policy,
3131 handler::branch_protection::list_policies::list_policies,
3232 handler::branch_protection::update_policy::update_policy,
33 handler::tag_protection::create_policy::create_policy,
34 handler::tag_protection::delete_policy::delete_policy,
35 handler::tag_protection::get_policy::get_policy,
36 handler::tag_protection::list_policies::list_policies,
37 handler::tag_protection::update_policy::update_policy,
3338 handler::commits::get::get_commit,
3439 handler::commits::is_ancestor::is_ancestor,
3540 handler::commits::list::list_commits,
@@ -129,7 +134,10 @@
129134 crate::service::commits::CommitDto,
130135 crate::service::protection::BranchPolicyDto,
131136 crate::service::protection::CreateBranchPolicyCmd,
132 crate::service::protection::UpdateBranchPolicyCmd,
137 crate::service::protection::UpdateBranchPolicyCmd, crate::service::protection::BranchPolicyFlags,
138 crate::service::protection::TagPolicyDto,
139 crate::service::protection::CreateTagPolicyCmd,
140 crate::service::protection::UpdateTagPolicyCmd,
133141 crate::service::commits::CommitStatusDto,
134142 crate::service::commits::CommitStatusDtoState,
135143 crate::service::commits::CommitsPageDto,
Modified lib/git/src/routes.rs View lib/git/src/routes.rs
@@ -303,6 +303,17 @@
303303 delete(remove_branch_whitelist),
304304 )
305305 .route(
306 "/{space_name}/{repo_name}/tag-protection",
307 get(crate::handler::tag_protection::list_policies::list_policies)
308 .post(crate::handler::tag_protection::create_policy::create_policy),
309 )
310 .route(
311 "/{space_name}/{repo_name}/tag-protection/{policy_id}",
312 get(crate::handler::tag_protection::get_policy::get_policy)
313 .patch(crate::handler::tag_protection::update_policy::update_policy)
314 .delete(crate::handler::tag_protection::delete_policy::delete_policy),
315 )
316 .route(
306317 "/{space_name}/{repo_name}/tag-protection/{policy_id}/whitelists",
307318 post(add_tag_whitelist).get(list_tag_whitelists),
308319 )
Modified lib/issues/src/services/boards.rs View lib/issues/src/services/boards.rs
@@ -18,7 +18,7 @@
1818use crate::services::issues::delete_issue_cascade_in_txn;
1919use crate::services::service::IssueService;
2020use crate::share::authz::require_board_manage;
21use crate::share::lookup::{lookup_board, lookup_space_by_name, require_member};
21use crate::share::lookup::{board_context, lookup_board, space_context};
2222
2323#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
2424pub struct BoardDto {
@@ -142,8 +142,7 @@
142142 space_name: &str,
143143 cmd: CreateBoardCmd,
144144 ) -> Result<BoardDto, IssueError> {
145 let space = lookup_space_by_name(self, space_name).await?;
146 require_member(self, &space, caller).await?;
145 let space = space_context(self, caller, space_name).await?;
147146 require_board_manage(self, &space, caller).await?;
148147
149148 let name = cmd.name.trim();
@@ -233,16 +232,9 @@
233232 space_name: &str,
234233 q: ListBoardQuery,
235234 ) -> Result<BoardPage, IssueError> {
236 let space = lookup_space_by_name(self, space_name).await?;
237 require_member(self, &space, caller).await?;
235 let space = space_context(self, caller, space_name).await?;
238236
239 let max_pagination = self.config().issues.max_pagination;
240 let default_pagination = self.config().issues.default_pagination;
241 let limit = q
242 .limit
243 .unwrap_or(default_pagination)
244 .clamp(1, max_pagination);
245 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
237 let (limit, offset) = self.paginate(q.limit, q.offset);
246238
247239 let dirs_in_space = dirs::Entity::find()
248240 .filter(dirs::Column::SpaceId.eq(Some(space.id)))
@@ -317,9 +309,7 @@
317309 space_name: &str,
318310 board_id: Uuid,
319311 ) -> Result<BoardDto, IssueError> {
320 let space = lookup_space_by_name(self, space_name).await?;
321 require_member(self, &space, caller).await?;
322 let board = lookup_board(self, &space, board_id, caller).await?;
312 let (_, board) = board_context(self, caller, space_name, board_id).await?;
323313 self.build_board_dto(board).await
324314 }
325315
@@ -330,8 +320,7 @@
330320 board_id: Uuid,
331321 cmd: UpdateBoardCmd,
332322 ) -> Result<BoardDto, IssueError> {
333 let space = lookup_space_by_name(self, space_name).await?;
334 require_member(self, &space, caller).await?;
323 let space = space_context(self, caller, space_name).await?;
335324 require_board_manage(self, &space, caller).await?;
336325 let board = lookup_board(self, &space, board_id, caller).await?;
337326
@@ -387,8 +376,7 @@
387376 space_name: &str,
388377 board_id: Uuid,
389378 ) -> Result<(), IssueError> {
390 let space = lookup_space_by_name(self, space_name).await?;
391 require_member(self, &space, caller).await?;
379 let space = space_context(self, caller, space_name).await?;
392380 require_board_manage(self, &space, caller).await?;
393381 let board = lookup_board(self, &space, board_id, caller).await?;
394382
Modified lib/issues/src/services/comments.rs View lib/issues/src/services/comments.rs
@@ -17,7 +17,7 @@
1717use crate::errors::IssueError;
1818use crate::services::service::IssueService;
1919use crate::share::authz::is_board_manager;
20use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
20use crate::share::lookup::{issue_context, require_issue_access};
2121
2222#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
2323pub struct CommentDto {
@@ -90,10 +90,7 @@
9090 "comment creation quota exceeded, retry tomorrow".to_string(),
9191 ));
9292 }
93 let space = lookup_space_by_name(self, space_name).await?;
94 require_member(self, &space, caller).await?;
95 let board = lookup_board(self, &space, board_id, caller).await?;
96 let item = lookup_issue(self, &board, issue_id).await?;
93 let (space, board, item) = issue_context(self, caller, space_name, board_id, issue_id).await?;
9794
9895 let content = cmd.content.trim();
9996 if content.is_empty() || content.chars().count() > self.config().issues.max_comment_len {
@@ -303,18 +300,9 @@
303300 issue_id: Uuid,
304301 q: ListCommentQuery,
305302 ) -> Result<CommentPage, IssueError> {
306 let space = lookup_space_by_name(self, space_name).await?;
307 require_member(self, &space, caller).await?;
308 let board = lookup_board(self, &space, board_id, caller).await?;
309 let _ = lookup_issue(self, &board, issue_id).await?;
310
311 let max_pagination = self.config().issues.max_pagination;
312 let default_pagination = self.config().issues.default_pagination;
313 let limit = q
314 .limit
315 .unwrap_or(default_pagination)
316 .clamp(1, max_pagination);
317 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
303 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
304
305 let (limit, offset) = self.paginate(q.limit, q.offset);
318306
319307 let mut select =
320308 comment::Entity::find().filter(comment::Column::IssueId.eq(Some(issue_id)));
@@ -365,10 +353,7 @@
365353 board_id: Uuid,
366354 issue_id: Uuid,
367355 ) -> Result<Vec<CommentTreeNode>, IssueError> {
368 let space = lookup_space_by_name(self, space_name).await?;
369 require_member(self, &space, caller).await?;
370 let board = lookup_board(self, &space, board_id, caller).await?;
371 let _ = lookup_issue(self, &board, issue_id).await?;
356 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
372357
373358 let rows = comment::Entity::find()
374359 .filter(comment::Column::IssueId.eq(Some(issue_id)))
@@ -426,10 +411,7 @@
426411 issue_id: Uuid,
427412 comment_id: Uuid,
428413 ) -> Result<CommentDto, IssueError> {
429 let space = lookup_space_by_name(self, space_name).await?;
430 require_member(self, &space, caller).await?;
431 let board = lookup_board(self, &space, board_id, caller).await?;
432 let _ = lookup_issue(self, &board, issue_id).await?;
414 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
433415
434416 let c = comment::Entity::find_by_id(comment_id)
435417 .filter(comment::Column::IssueId.eq(Some(issue_id)))
@@ -460,10 +442,7 @@
460442 comment_id: Uuid,
461443 cmd: UpdateCommentCmd,
462444 ) -> Result<CommentDto, IssueError> {
463 let space = lookup_space_by_name(self, space_name).await?;
464 require_member(self, &space, caller).await?;
465 let board = lookup_board(self, &space, board_id, caller).await?;
466 let _ = lookup_issue(self, &board, issue_id).await?;
445 let (space, _, _) = issue_context(self, caller, space_name, board_id, issue_id).await?;
467446
468447 let c = comment::Entity::find_by_id(comment_id)
469448 .filter(comment::Column::IssueId.eq(Some(issue_id)))
@@ -549,10 +528,7 @@
549528 issue_id: Uuid,
550529 comment_id: Uuid,
551530 ) -> Result<(), IssueError> {
552 let space = lookup_space_by_name(self, space_name).await?;
553 require_member(self, &space, caller).await?;
554 let board = lookup_board(self, &space, board_id, caller).await?;
555 let _ = lookup_issue(self, &board, issue_id).await?;
531 let (space, _, _) = issue_context(self, caller, space_name, board_id, issue_id).await?;
556532
557533 let c = comment::Entity::find_by_id(comment_id)
558534 .filter(comment::Column::IssueId.eq(Some(issue_id)))
Modified lib/issues/src/services/content_history.rs View lib/issues/src/services/content_history.rs
@@ -8,7 +8,7 @@
88
99use crate::errors::IssueError;
1010use crate::services::service::IssueService;
11use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
11use crate::share::lookup::{require_issue_access};
1212
1313#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1414pub struct ContentHistoryDto {
@@ -47,18 +47,9 @@
4747 issue_id: Uuid,
4848 q: ListContentHistoryQuery,
4949 ) -> Result<ContentHistoryPage, IssueError> {
50 let space = lookup_space_by_name(self, space_name).await?;
51 require_member(self, &space, caller).await?;
52 let board = lookup_board(self, &space, board_id, caller).await?;
53 let _ = lookup_issue(self, &board, issue_id).await?;
50 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
5451
55 let max_pagination = self.config().issues.max_pagination;
56 let default_pagination = self.config().issues.default_pagination;
57 let limit = q
58 .limit
59 .unwrap_or(default_pagination)
60 .clamp(1, max_pagination);
61 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
52 let (limit, offset) = self.paginate(q.limit, q.offset);
6253
6354 let mut select = issue_content_history::Entity::find()
6455 .filter(issue_content_history::Column::IssueId.eq(issue_id));
Modified lib/issues/src/services/dependencies.rs View lib/issues/src/services/dependencies.rs
@@ -11,7 +11,7 @@
1111use crate::errors::IssueError;
1212use crate::services::service::IssueService;
1313use crate::share::authz::require_board_manage;
14use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
14use crate::share::lookup::{lookup_board, lookup_issue, require_issue_access, space_context};
1515
1616#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1717pub struct DependencyDto {
@@ -54,8 +54,7 @@
5454 issue_id: Uuid,
5555 cmd: CreateDependencyCmd,
5656 ) -> Result<DependencyDto, IssueError> {
57 let space = lookup_space_by_name(self, space_name).await?;
58 require_member(self, &space, caller).await?;
57 let space = space_context(self, caller, space_name).await?;
5958 require_board_manage(self, &space, caller).await?;
6059 let board = lookup_board(self, &space, board_id, caller).await?;
6160 let _ = lookup_issue(self, &board, issue_id).await?;
@@ -125,10 +124,7 @@
125124 board_id: Uuid,
126125 issue_id: Uuid,
127126 ) -> Result<Vec<DependencyDto>, IssueError> {
128 let space = lookup_space_by_name(self, space_name).await?;
129 require_member(self, &space, caller).await?;
130 let board = lookup_board(self, &space, board_id, caller).await?;
131 let _ = lookup_issue(self, &board, issue_id).await?;
127 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
132128
133129 let rows = issue_dependency::Entity::find()
134130 .filter(issue_dependency::Column::DependencyId.eq(issue_id))
@@ -146,10 +142,7 @@
146142 board_id: Uuid,
147143 issue_id: Uuid,
148144 ) -> Result<Vec<DependencyDto>, IssueError> {
149 let space = lookup_space_by_name(self, space_name).await?;
150 require_member(self, &space, caller).await?;
151 let board = lookup_board(self, &space, board_id, caller).await?;
152 let _ = lookup_issue(self, &board, issue_id).await?;
145 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
153146
154147 let rows = issue_dependency::Entity::find()
155148 .filter(issue_dependency::Column::IssueId.eq(issue_id))
@@ -168,8 +161,7 @@
168161 issue_id: Uuid,
169162 dep_id: Uuid,
170163 ) -> Result<(), IssueError> {
171 let space = lookup_space_by_name(self, space_name).await?;
172 require_member(self, &space, caller).await?;
164 let space = space_context(self, caller, space_name).await?;
173165 require_board_manage(self, &space, caller).await?;
174166 let board = lookup_board(self, &space, board_id, caller).await?;
175167 let _ = lookup_issue(self, &board, issue_id).await?;
Modified lib/issues/src/services/events.rs View lib/issues/src/services/events.rs
@@ -8,7 +8,7 @@
88
99use crate::errors::IssueError;
1010use crate::services::service::IssueService;
11use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
11use crate::share::lookup::{require_issue_access};
1212
1313#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1414pub struct EventDto {
@@ -50,18 +50,9 @@
5050 issue_id: Uuid,
5151 q: ListEventQuery,
5252 ) -> Result<EventPage, IssueError> {
53 let space = lookup_space_by_name(self, space_name).await?;
54 require_member(self, &space, caller).await?;
55 let board = lookup_board(self, &space, board_id, caller).await?;
56 let _ = lookup_issue(self, &board, issue_id).await?;
53 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
5754
58 let max_pagination = self.config().issues.max_pagination;
59 let default_pagination = self.config().issues.default_pagination;
60 let limit = q
61 .limit
62 .unwrap_or(default_pagination)
63 .clamp(1, max_pagination);
64 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
55 let (limit, offset) = self.paginate(q.limit, q.offset);
6556
6657 let select = issue_event_line::Entity::find()
6758 .filter(issue_event_line::Column::IssueId.eq(Some(issue_id)))
Modified lib/issues/src/services/issue_users.rs View lib/issues/src/services/issue_users.rs
@@ -11,7 +11,7 @@
1111
1212use crate::errors::IssueError;
1313use crate::services::service::IssueService;
14use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
14use crate::share::lookup::{require_issue_access};
1515
1616#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1717pub struct IssueUserDto {
@@ -54,18 +54,9 @@
5454 issue_id: Uuid,
5555 q: ListIssueUserQuery,
5656 ) -> Result<IssueUserPage, IssueError> {
57 let space = lookup_space_by_name(self, space_name).await?;
58 require_member(self, &space, caller).await?;
59 let board = lookup_board(self, &space, board_id, caller).await?;
60 let _ = lookup_issue(self, &board, issue_id).await?;
57 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
6158
62 let max_pagination = self.config().issues.max_pagination;
63 let default_pagination = self.config().issues.default_pagination;
64 let limit = q
65 .limit
66 .unwrap_or(default_pagination)
67 .clamp(1, max_pagination);
68 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
59 let (limit, offset) = self.paginate(q.limit, q.offset);
6960
7061 let select = issue_user::Entity::find()
7162 .filter(issue_user::Column::IssueId.eq(issue_id))
@@ -108,10 +99,7 @@
10899 uid: Uuid,
109100 cmd: UpdateIssueUserCmd,
110101 ) -> Result<IssueUserDto, IssueError> {
111 let space = lookup_space_by_name(self, space_name).await?;
112 require_member(self, &space, caller).await?;
113 let board = lookup_board(self, &space, board_id, caller).await?;
114 let _ = lookup_issue(self, &board, issue_id).await?;
102 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
115103
116104 if caller != uid {
117105 return Err(IssueError::Forbidden);
Modified lib/issues/src/services/issues.rs View lib/issues/src/services/issues.rs
@@ -33,7 +33,7 @@
3333use crate::errors::IssueError;
3434use crate::services::service::IssueService;
3535use crate::share::authz::is_board_manager;
36use crate::share::lookup::{lookup_board, lookup_issue, lookup_issue_by_ref, lookup_space_by_name, require_member};
36use crate::share::lookup::{board_context, issue_context, lookup_issue_by_ref};
3737
3838#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
3939#[serde(rename_all = "snake_case")]
@@ -326,9 +326,7 @@
326326 "issue creation quota exceeded, retry tomorrow".to_string(),
327327 ));
328328 }
329 let space = lookup_space_by_name(self, space_name).await?;
330 require_member(self, &space, caller).await?;
331 let board = lookup_board(self, &space, board_id, caller).await?;
329 let (space, board) = board_context(self, caller, space_name, board_id).await?;
332330
333331 let title = cmd.title.trim();
334332 if title.is_empty() || title.chars().count() > self.config().issues.max_title_len {
@@ -551,17 +549,9 @@
551549 board_id: Uuid,
552550 q: ListIssueQuery,
553551 ) -> Result<IssuePage, IssueError> {
554 let space = lookup_space_by_name(self, space_name).await?;
555 require_member(self, &space, caller).await?;
556 let board = lookup_board(self, &space, board_id, caller).await?;
557
558 let max_pagination = self.config().issues.max_pagination;
559 let default_pagination = self.config().issues.default_pagination;
560 let limit = q
561 .limit
562 .unwrap_or(default_pagination)
563 .clamp(1, max_pagination);
564 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
552 let (_, board) = board_context(self, caller, space_name, board_id).await?;
553
554 let (limit, offset) = self.paginate(q.limit, q.offset);
565555
566556 let mut select =
567557 issue_item::Entity::find().filter(issue_item::Column::CollectionId.eq(board.id));
@@ -680,10 +670,7 @@
680670 board_id: Uuid,
681671 issue_id: Uuid,
682672 ) -> Result<IssueDto, IssueError> {
683 let space = lookup_space_by_name(self, space_name).await?;
684 require_member(self, &space, caller).await?;
685 let board = lookup_board(self, &space, board_id, caller).await?;
686 let item = lookup_issue(self, &board, issue_id).await?;
673 let (_, _, item) = issue_context(self, caller, space_name, board_id, issue_id).await?;
687674
688675 self.build_issue_dto(caller, item).await
689676 }
@@ -696,10 +683,7 @@
696683 issue_id: Uuid,
697684 cmd: UpdateIssueCmd,
698685 ) -> Result<IssueDto, IssueError> {
699 let space = lookup_space_by_name(self, space_name).await?;
700 require_member(self, &space, caller).await?;
701 let board = lookup_board(self, &space, board_id, caller).await?;
702 let item = lookup_issue(self, &board, issue_id).await?;
686 let (space, board, item) = issue_context(self, caller, space_name, board_id, issue_id).await?;
703687
704688 if item.author_id != caller && !is_board_manager(self, &space, caller).await? {
705689 return Err(IssueError::Forbidden);
@@ -1000,9 +984,7 @@
1000984 "issue_refs must contain 1..=100 entries".to_string(),
1001985 ));
1002986 }
1003 let space = lookup_space_by_name(self, space_name).await?;
1004 require_member(self, &space, caller).await?;
1005 let board = lookup_board(self, &space, board_id, caller).await?;
987 let (space, board) = board_context(self, caller, space_name, board_id).await?;
1006988 let is_manager = is_board_manager(self, &space, caller).await?;
1007989
1008990 let mut updated = 0u64;
@@ -1057,10 +1039,7 @@
10571039 board_id: Uuid,
10581040 issue_id: Uuid,
10591041 ) -> Result<(), IssueError> {
1060 let space = lookup_space_by_name(self, space_name).await?;
1061 require_member(self, &space, caller).await?;
1062 let board = lookup_board(self, &space, board_id, caller).await?;
1063 let item = lookup_issue(self, &board, issue_id).await?;
1042 let (space, _, item) = issue_context(self, caller, space_name, board_id, issue_id).await?;
10641043
10651044 if item.author_id != caller && !is_board_manager(self, &space, caller).await? {
10661045 return Err(IssueError::Forbidden);
@@ -1087,10 +1066,7 @@
10871066 issue_id: Uuid,
10881067 closed: bool,
10891068 ) -> Result<IssueDto, IssueError> {
1090 let space = lookup_space_by_name(self, space_name).await?;
1091 require_member(self, &space, caller).await?;
1092 let board = lookup_board(self, &space, board_id, caller).await?;
1093 let item = lookup_issue(self, &board, issue_id).await?;
1069 let (space, _, item) = issue_context(self, caller, space_name, board_id, issue_id).await?;
10941070
10951071 if item.author_id != caller && !is_board_manager(self, &space, caller).await? {
10961072 return Err(IssueError::Forbidden);
Modified lib/issues/src/services/labels.rs View lib/issues/src/services/labels.rs
@@ -13,7 +13,7 @@
1313use crate::errors::IssueError;
1414use crate::services::service::IssueService;
1515use crate::share::authz::require_board_manage;
16use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
16use crate::share::lookup::{board_context, lookup_board, lookup_issue, require_issue_access, space_context};
1717
1818#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1919pub struct LabelDto {
@@ -116,8 +116,7 @@
116116 issue_id: Uuid,
117117 label_id: Uuid,
118118 ) -> Result<issue_label::Model, IssueError> {
119 let space = lookup_space_by_name(self, space_name).await?;
120 require_member(self, &space, caller).await?;
119 let space = space_context(self, caller, space_name).await?;
121120 require_board_manage(self, &space, caller).await?;
122121 let board = lookup_board(self, &space, board_id, caller).await?;
123122 let _ = lookup_issue(self, &board, issue_id).await?;
@@ -136,8 +135,7 @@
136135 board_id: Uuid,
137136 cmd: CreateLabelCmd,
138137 ) -> Result<LabelDto, IssueError> {
139 let space = lookup_space_by_name(self, space_name).await?;
140 require_member(self, &space, caller).await?;
138 let space = space_context(self, caller, space_name).await?;
141139 require_board_manage(self, &space, caller).await?;
142140 let board = lookup_board(self, &space, board_id, caller).await?;
143141
@@ -188,9 +186,7 @@
188186 board_id: Uuid,
189187 q: ListLabelQuery,
190188 ) -> Result<LabelPage, IssueError> {
191 let space = lookup_space_by_name(self, space_name).await?;
192 require_member(self, &space, caller).await?;
193 let board = lookup_board(self, &space, board_id, caller).await?;
189 let (_, board) = board_context(self, caller, space_name, board_id).await?;
194190
195191 let max_pagination = self.config().issues.max_pagination;
196192 let default_pagination = self.config().issues.default_pagination;
@@ -234,9 +230,7 @@
234230 board_id: Uuid,
235231 label_id: Uuid,
236232 ) -> Result<LabelDto, IssueError> {
237 let space = lookup_space_by_name(self, space_name).await?;
238 require_member(self, &space, caller).await?;
239 let board = lookup_board(self, &space, board_id, caller).await?;
233 let (_, board) = board_context(self, caller, space_name, board_id).await?;
240234
241235 let l = issue_label::Entity::find_by_id(label_id)
242236 .filter(issue_label::Column::DirId.eq(board.dir_id))
@@ -255,9 +249,7 @@
255249 label_id: Uuid,
256250 cmd: UpdateLabelCmd,
257251 ) -> Result<LabelDto, IssueError> {
258 let space = lookup_space_by_name(self, space_name).await?;
259 require_member(self, &space, caller).await?;
260 let board = lookup_board(self, &space, board_id, caller).await?;
252 let (_, board) = board_context(self, caller, space_name, board_id).await?;
261253
262254 let l = issue_label::Entity::find_by_id(label_id)
263255 .filter(issue_label::Column::DirId.eq(board.dir_id))
@@ -319,9 +311,7 @@
319311 board_id: Uuid,
320312 label_id: Uuid,
321313 ) -> Result<(), IssueError> {
322 let space = lookup_space_by_name(self, space_name).await?;
323 require_member(self, &space, caller).await?;
324 let board = lookup_board(self, &space, board_id, caller).await?;
314 let (_, board) = board_context(self, caller, space_name, board_id).await?;
325315
326316 let _ = issue_label::Entity::find_by_id(label_id)
327317 .filter(issue_label::Column::DirId.eq(board.dir_id))
@@ -355,10 +345,7 @@
355345 board_id: Uuid,
356346 issue_id: Uuid,
357347 ) -> Result<Vec<LabelDto>, IssueError> {
358 let space = lookup_space_by_name(self, space_name).await?;
359 require_member(self, &space, caller).await?;
360 let board = lookup_board(self, &space, board_id, caller).await?;
361 let _ = lookup_issue(self, &board, issue_id).await?;
348 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
362349
363350 let rels = issue_relation_label::Entity::find()
364351 .filter(issue_relation_label::Column::IssueId.eq(issue_id))
Modified lib/issues/src/services/members.rs View lib/issues/src/services/members.rs
@@ -15,7 +15,7 @@
1515use crate::errors::IssueError;
1616use crate::services::service::IssueService;
1717use crate::share::authz::require_board_manage;
18use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
18use crate::share::lookup::{lookup_board, lookup_issue, require_issue_access, space_context};
1919
2020#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
2121pub struct IssueMemberDto {
@@ -59,8 +59,7 @@
5959 issue_id: Uuid,
6060 cmd: CreateIssueMemberCmd,
6161 ) -> Result<IssueMemberDto, IssueError> {
62 let space = lookup_space_by_name(self, space_name).await?;
63 require_member(self, &space, caller).await?;
62 let space = space_context(self, caller, space_name).await?;
6463 require_board_manage(self, &space, caller).await?;
6564 let board = lookup_board(self, &space, board_id, caller).await?;
6665 let item = lookup_issue(self, &board, issue_id).await?;
@@ -202,18 +201,9 @@
202201 issue_id: Uuid,
203202 q: ListIssueMemberQuery,
204203 ) -> Result<IssueMemberPage, IssueError> {
205 let space = lookup_space_by_name(self, space_name).await?;
206 require_member(self, &space, caller).await?;
207 let board = lookup_board(self, &space, board_id, caller).await?;
208 let _ = lookup_issue(self, &board, issue_id).await?;
204 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
209205
210 let max_pagination = self.config().issues.max_pagination;
211 let default_pagination = self.config().issues.default_pagination;
212 let limit = q
213 .limit
214 .unwrap_or(default_pagination)
215 .clamp(1, max_pagination);
216 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
206 let (limit, offset) = self.paginate(q.limit, q.offset);
217207
218208 let mut select =
219209 issue_member::Entity::find().filter(issue_member::Column::IssueId.eq(issue_id));
@@ -260,8 +250,7 @@
260250 issue_id: Uuid,
261251 member_id: Uuid,
262252 ) -> Result<(), IssueError> {
263 let space = lookup_space_by_name(self, space_name).await?;
264 require_member(self, &space, caller).await?;
253 let space = space_context(self, caller, space_name).await?;
265254 require_board_manage(self, &space, caller).await?;
266255 let board = lookup_board(self, &space, board_id, caller).await?;
267256 let _ = lookup_issue(self, &board, issue_id).await?;
Modified lib/issues/src/services/milestones.rs View lib/issues/src/services/milestones.rs
@@ -15,7 +15,7 @@
1515use crate::errors::IssueError;
1616use crate::services::service::IssueService;
1717use crate::share::authz::require_board_manage;
18use crate::share::lookup::{lookup_board, lookup_space_by_name, require_member};
18use crate::share::lookup::{board_context, lookup_board, space_context};
1919
2020#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
2121pub struct MilestoneDto {
@@ -148,8 +148,7 @@
148148 board_id: Uuid,
149149 cmd: CreateMilestoneCmd,
150150 ) -> Result<MilestoneDto, IssueError> {
151 let space = lookup_space_by_name(self, space_name).await?;
152 require_member(self, &space, caller).await?;
151 let space = space_context(self, caller, space_name).await?;
153152 require_board_manage(self, &space, caller).await?;
154153 let board = lookup_board(self, &space, board_id, caller).await?;
155154
@@ -206,17 +205,9 @@
206205 board_id: Uuid,
207206 q: ListMilestoneQuery,
208207 ) -> Result<MilestonePage, IssueError> {
209 let space = lookup_space_by_name(self, space_name).await?;
210 require_member(self, &space, caller).await?;
211 let board = lookup_board(self, &space, board_id, caller).await?;
208 let (_, board) = board_context(self, caller, space_name, board_id).await?;
212209
213 let max_pagination = self.config().issues.max_pagination;
214 let default_pagination = self.config().issues.default_pagination;
215 let limit = q
216 .limit
217 .unwrap_or(default_pagination)
218 .clamp(1, max_pagination);
219 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
210 let (limit, offset) = self.paginate(q.limit, q.offset);
220211
221212 let mut select =
222213 milestone::Entity::find().filter(milestone::Column::CollectionId.eq(board.id));
@@ -263,9 +254,7 @@
263254 board_id: Uuid,
264255 milestone_id: Uuid,
265256 ) -> Result<MilestoneDto, IssueError> {
266 let space = lookup_space_by_name(self, space_name).await?;
267 require_member(self, &space, caller).await?;
268 let board = lookup_board(self, &space, board_id, caller).await?;
257 let (_, board) = board_context(self, caller, space_name, board_id).await?;
269258
270259 let m = milestone::Entity::find_by_id(milestone_id)
271260 .filter(milestone::Column::CollectionId.eq(board.id))
@@ -284,8 +273,7 @@
284273 milestone_id: Uuid,
285274 cmd: UpdateMilestoneCmd,
286275 ) -> Result<MilestoneDto, IssueError> {
287 let space = lookup_space_by_name(self, space_name).await?;
288 require_member(self, &space, caller).await?;
276 let space = space_context(self, caller, space_name).await?;
289277 require_board_manage(self, &space, caller).await?;
290278 let board = lookup_board(self, &space, board_id, caller).await?;
291279
@@ -341,8 +329,7 @@
341329 board_id: Uuid,
342330 milestone_id: Uuid,
343331 ) -> Result<(), IssueError> {
344 let space = lookup_space_by_name(self, space_name).await?;
345 require_member(self, &space, caller).await?;
332 let space = space_context(self, caller, space_name).await?;
346333 require_board_manage(self, &space, caller).await?;
347334 let board = lookup_board(self, &space, board_id, caller).await?;
348335
Modified lib/issues/src/services/pins.rs View lib/issues/src/services/pins.rs
@@ -8,7 +8,7 @@
88use crate::errors::IssueError;
99use crate::services::service::IssueService;
1010use crate::share::authz::require_board_manage;
11use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
11use crate::share::lookup::{board_context, lookup_board, lookup_issue, space_context};
1212
1313#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1414pub struct PinDto {
@@ -58,8 +58,7 @@
5858 pin_id: Uuid,
5959 cmd: UpdatePinCmd,
6060 ) -> Result<PinDto, IssueError> {
61 let space = lookup_space_by_name(self, space_name).await?;
62 require_member(self, &space, caller).await?;
61 let space = space_context(self, caller, space_name).await?;
6362 require_board_manage(self, &space, caller).await?;
6463 let row = issue_pin::Entity::find_by_id(pin_id)
6564 .filter(issue_pin::Column::CollectionId.eq(board_id))
@@ -84,8 +83,7 @@
8483 board_id: Uuid,
8584 cmd: CreatePinCmd,
8685 ) -> Result<PinDto, IssueError> {
87 let space = lookup_space_by_name(self, space_name).await?;
88 require_member(self, &space, caller).await?;
86 let space = space_context(self, caller, space_name).await?;
8987 require_board_manage(self, &space, caller).await?;
9088 let board = lookup_board(self, &space, board_id, caller).await?;
9189 let _ = lookup_issue(self, &board, cmd.issue_id).await?;
@@ -126,9 +124,7 @@
126124 space_name: &str,
127125 board_id: Uuid,
128126 ) -> Result<Vec<PinDto>, IssueError> {
129 let space = lookup_space_by_name(self, space_name).await?;
130 require_member(self, &space, caller).await?;
131 let board = lookup_board(self, &space, board_id, caller).await?;
127 let (_, board) = board_context(self, caller, space_name, board_id).await?;
132128
133129 let rows = issue_pin::Entity::find()
134130 .filter(issue_pin::Column::CollectionId.eq(board.id))
@@ -147,8 +143,7 @@
147143 board_id: Uuid,
148144 pin_id: Uuid,
149145 ) -> Result<(), IssueError> {
150 let space = lookup_space_by_name(self, space_name).await?;
151 require_member(self, &space, caller).await?;
146 let space = space_context(self, caller, space_name).await?;
152147 require_board_manage(self, &space, caller).await?;
153148 let board = lookup_board(self, &space, board_id, caller).await?;
154149
Modified lib/issues/src/services/reactions.rs View lib/issues/src/services/reactions.rs
@@ -13,7 +13,7 @@
1313
1414use crate::errors::IssueError;
1515use crate::services::service::IssueService;
16use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
16use crate::share::lookup::{issue_context, require_issue_access};
1717
1818#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
1919#[serde(rename_all = "snake_case")]
@@ -103,10 +103,7 @@
103103 issue_id: Uuid,
104104 cmd: CreateReactionCmd,
105105 ) -> Result<ReactionDto, IssueError> {
106 let space = lookup_space_by_name(self, space_name).await?;
107 require_member(self, &space, caller).await?;
108 let board = lookup_board(self, &space, board_id, caller).await?;
109 let _ = lookup_issue(self, &board, issue_id).await?;
106 let (space, _, _) = issue_context(self, caller, space_name, board_id, issue_id).await?;
110107
111108 let emoji = self.validate_emoji(&cmd.emoji)?;
112109
@@ -166,10 +163,7 @@
166163 comment_id: Uuid,
167164 cmd: CreateReactionCmd,
168165 ) -> Result<ReactionDto, IssueError> {
169 let space = lookup_space_by_name(self, space_name).await?;
170 require_member(self, &space, caller).await?;
171 let board = lookup_board(self, &space, board_id, caller).await?;
172 let _ = lookup_issue(self, &board, issue_id).await?;
166 let (space, _, _) = issue_context(self, caller, space_name, board_id, issue_id).await?;
173167
174168 let c = comment::Entity::find_by_id(comment_id)
175169 .filter(comment::Column::IssueId.eq(Some(issue_id)))
@@ -233,10 +227,7 @@
233227 board_id: Uuid,
234228 issue_id: Uuid,
235229 ) -> Result<Vec<ReactionAggDto>, IssueError> {
236 let space = lookup_space_by_name(self, space_name).await?;
237 require_member(self, &space, caller).await?;
238 let board = lookup_board(self, &space, board_id, caller).await?;
239 let _ = lookup_issue(self, &board, issue_id).await?;
230 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
240231
241232 let rows = issue_reactions::Entity::find()
242233 .filter(issue_reactions::Column::IssueId.eq(Some(issue_id)))
@@ -254,10 +245,7 @@
254245 issue_id: Uuid,
255246 comment_id: Uuid,
256247 ) -> Result<Vec<ReactionAggDto>, IssueError> {
257 let space = lookup_space_by_name(self, space_name).await?;
258 require_member(self, &space, caller).await?;
259 let board = lookup_board(self, &space, board_id, caller).await?;
260 let _ = lookup_issue(self, &board, issue_id).await?;
248 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
261249
262250 let _ = comment::Entity::find_by_id(comment_id)
263251 .filter(comment::Column::IssueId.eq(Some(issue_id)))
@@ -281,10 +269,7 @@
281269 issue_id: Uuid,
282270 emoji: &str,
283271 ) -> Result<(), IssueError> {
284 let space = lookup_space_by_name(self, space_name).await?;
285 require_member(self, &space, caller).await?;
286 let board = lookup_board(self, &space, board_id, caller).await?;
287 let _ = lookup_issue(self, &board, issue_id).await?;
272 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
288273
289274 let emoji = self.validate_emoji(emoji)?;
290275 let row = issue_reactions::Entity::find()
@@ -310,10 +295,7 @@
310295 comment_id: Uuid,
311296 emoji: &str,
312297 ) -> Result<(), IssueError> {
313 let space = lookup_space_by_name(self, space_name).await?;
314 require_member(self, &space, caller).await?;
315 let board = lookup_board(self, &space, board_id, caller).await?;
316 let _ = lookup_issue(self, &board, issue_id).await?;
298 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
317299 let _ = comment::Entity::find_by_id(comment_id)
318300 .filter(comment::Column::IssueId.eq(Some(issue_id)))
319301 .one(self.db().read())
@@ -342,10 +324,7 @@
342324 issue_id: Uuid,
343325 reaction_id: Uuid,
344326 ) -> Result<(), IssueError> {
345 let space = lookup_space_by_name(self, space_name).await?;
346 require_member(self, &space, caller).await?;
347 let board = lookup_board(self, &space, board_id, caller).await?;
348 let _ = lookup_issue(self, &board, issue_id).await?;
327 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
349328
350329 let r = issue_reactions::Entity::find_by_id(reaction_id)
351330 .filter(issue_reactions::Column::UserId.eq(caller))
Modified lib/issues/src/services/repo_relations.rs View lib/issues/src/services/repo_relations.rs
@@ -9,7 +9,7 @@
99use crate::errors::IssueError;
1010use crate::services::service::IssueService;
1111use crate::share::authz::require_board_manage;
12use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
12use crate::share::lookup::{lookup_board, lookup_issue, require_issue_access, space_context};
1313
1414#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1515pub struct RepoRelDto {
@@ -52,8 +52,7 @@
5252 issue_id: Uuid,
5353 cmd: CreateRepoRelCmd,
5454 ) -> Result<RepoRelDto, IssueError> {
55 let space = lookup_space_by_name(self, space_name).await?;
56 require_member(self, &space, caller).await?;
55 let space = space_context(self, caller, space_name).await?;
5756 require_board_manage(self, &space, caller).await?;
5857 let board = lookup_board(self, &space, board_id, caller).await?;
5958 let _ = lookup_issue(self, &board, issue_id).await?;
@@ -94,10 +93,7 @@
9493 board_id: Uuid,
9594 issue_id: Uuid,
9695 ) -> Result<Vec<RepoRelDto>, IssueError> {
97 let space = lookup_space_by_name(self, space_name).await?;
98 require_member(self, &space, caller).await?;
99 let board = lookup_board(self, &space, board_id, caller).await?;
100 let _ = lookup_issue(self, &board, issue_id).await?;
96 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
10197
10298 let rows = issue_relation_repo::Entity::find()
10399 .filter(issue_relation_repo::Column::IssueId.eq(issue_id))
@@ -116,8 +112,7 @@
116112 issue_id: Uuid,
117113 rel_id: Uuid,
118114 ) -> Result<(), IssueError> {
119 let space = lookup_space_by_name(self, space_name).await?;
120 require_member(self, &space, caller).await?;
115 let space = space_context(self, caller, space_name).await?;
121116 require_board_manage(self, &space, caller).await?;
122117 let board = lookup_board(self, &space, board_id, caller).await?;
123118 let _ = lookup_issue(self, &board, issue_id).await?;
Modified lib/issues/src/services/service.rs View lib/issues/src/services/service.rs
@@ -34,6 +34,15 @@
3434 &self.state.config
3535 }
3636
37 pub fn paginate(&self, limit: Option<u64>, offset: Option<u64>) -> (u64, u64) {
38 let max_pagination = self.config().issues.max_pagination;
39 let default_pagination = self.config().issues.default_pagination;
40 (
41 limit.unwrap_or(default_pagination).clamp(1, max_pagination),
42 offset.unwrap_or(0).clamp(0, 5000),
43 )
44 }
45
3746 pub fn db(&self) -> &Arc<AppDatabase> {
3847 &self.state.database
3948 }
Modified lib/issues/src/services/stopwatch.rs View lib/issues/src/services/stopwatch.rs
@@ -9,7 +9,7 @@
99
1010use crate::errors::IssueError;
1111use crate::services::service::IssueService;
12use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
12use crate::share::lookup::{require_issue_access};
1313
1414#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1515pub struct StopwatchDto {
@@ -40,10 +40,7 @@
4040 board_id: Uuid,
4141 issue_id: Uuid,
4242 ) -> Result<StopwatchDto, IssueError> {
43 let space = lookup_space_by_name(self, space_name).await?;
44 require_member(self, &space, caller).await?;
45 let board = lookup_board(self, &space, board_id, caller).await?;
46 let _ = lookup_issue(self, &board, issue_id).await?;
43 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
4744
4845 let sw = stopwatch::Entity::find()
4946 .filter(stopwatch::Column::IssueId.eq(issue_id))
@@ -82,10 +79,7 @@
8279 board_id: Uuid,
8380 issue_id: Uuid,
8481 ) -> Result<StopwatchDto, IssueError> {
85 let space = lookup_space_by_name(self, space_name).await?;
86 require_member(self, &space, caller).await?;
87 let board = lookup_board(self, &space, board_id, caller).await?;
88 let _ = lookup_issue(self, &board, issue_id).await?;
82 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
8983
9084 let existing = stopwatch::Entity::find()
9185 .filter(stopwatch::Column::IssueId.eq(issue_id))
@@ -129,10 +123,7 @@
129123 board_id: Uuid,
130124 issue_id: Uuid,
131125 ) -> Result<(), IssueError> {
132 let space = lookup_space_by_name(self, space_name).await?;
133 require_member(self, &space, caller).await?;
134 let board = lookup_board(self, &space, board_id, caller).await?;
135 let _ = lookup_issue(self, &board, issue_id).await?;
126 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
136127
137128 let sw = stopwatch::Entity::find()
138129 .filter(stopwatch::Column::IssueId.eq(issue_id))
Modified lib/issues/src/services/tracked_time.rs View lib/issues/src/services/tracked_time.rs
@@ -8,7 +8,7 @@
88
99use crate::errors::IssueError;
1010use crate::services::service::IssueService;
11use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
11use crate::share::lookup::{require_issue_access};
1212
1313#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1414pub struct TrackedTimeDto {
@@ -65,10 +65,7 @@
6565 if cmd.seconds <= 0 {
6666 return Err(IssueError::BadRequest("seconds must be positive".to_string()));
6767 }
68 let space = lookup_space_by_name(self, space_name).await?;
69 require_member(self, &space, caller).await?;
70 let board = lookup_board(self, &space, board_id, caller).await?;
71 let _ = lookup_issue(self, &board, issue_id).await?;
68 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
7269 let row = tracked_time::ActiveModel {
7370 id: Set(Uuid::now_v7()),
7471 issue_id: Set(issue_id),
@@ -100,10 +97,7 @@
10097 if cmd.seconds <= 0 {
10198 return Err(IssueError::BadRequest("seconds must be positive".to_string()));
10299 }
103 let space = lookup_space_by_name(self, space_name).await?;
104 require_member(self, &space, caller).await?;
105 let board = lookup_board(self, &space, board_id, caller).await?;
106 let _ = lookup_issue(self, &board, issue_id).await?;
100 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
107101 let row = tracked_time::Entity::find_by_id(time_id)
108102 .filter(tracked_time::Column::IssueId.eq(issue_id))
109103 .one(self.db().write())
@@ -133,10 +127,7 @@
133127 issue_id: Uuid,
134128 time_id: Uuid,
135129 ) -> Result<(), IssueError> {
136 let space = lookup_space_by_name(self, space_name).await?;
137 require_member(self, &space, caller).await?;
138 let board = lookup_board(self, &space, board_id, caller).await?;
139 let _ = lookup_issue(self, &board, issue_id).await?;
130 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
140131 let row = tracked_time::Entity::find_by_id(time_id)
141132 .filter(tracked_time::Column::IssueId.eq(issue_id))
142133 .one(self.db().read())
@@ -159,18 +150,9 @@
159150 issue_id: Uuid,
160151 q: ListTrackedTimeQuery,
161152 ) -> Result<TrackedTimePage, IssueError> {
162 let space = lookup_space_by_name(self, space_name).await?;
163 require_member(self, &space, caller).await?;
164 let board = lookup_board(self, &space, board_id, caller).await?;
165 let _ = lookup_issue(self, &board, issue_id).await?;
153 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
166154
167 let max_pagination = self.config().issues.max_pagination;
168 let default_pagination = self.config().issues.default_pagination;
169 let limit = q
170 .limit
171 .unwrap_or(default_pagination)
172 .clamp(1, max_pagination);
173 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
155 let (limit, offset) = self.paginate(q.limit, q.offset);
174156
175157 let mut select =
176158 tracked_time::Entity::find().filter(tracked_time::Column::IssueId.eq(issue_id));
Modified lib/issues/src/services/watches.rs View lib/issues/src/services/watches.rs
@@ -9,7 +9,7 @@
99
1010use crate::errors::IssueError;
1111use crate::services::service::IssueService;
12use crate::share::lookup::{lookup_board, lookup_issue, lookup_space_by_name, require_member};
12use crate::share::lookup::{require_issue_access};
1313
1414#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1515pub struct WatchDto {
@@ -41,10 +41,7 @@
4141 board_id: Uuid,
4242 issue_id: Uuid,
4343 ) -> Result<WatchDto, IssueError> {
44 let space = lookup_space_by_name(self, space_name).await?;
45 require_member(self, &space, caller).await?;
46 let board = lookup_board(self, &space, board_id, caller).await?;
47 let _ = lookup_issue(self, &board, issue_id).await?;
44 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
4845
4946 let w = issue_watch::Entity::find()
5047 .filter(issue_watch::Column::IssueId.eq(Some(issue_id)))
@@ -74,10 +71,7 @@
7471 issue_id: Uuid,
7572 cmd: UpsertWatchCmd,
7673 ) -> Result<WatchDto, IssueError> {
77 let space = lookup_space_by_name(self, space_name).await?;
78 require_member(self, &space, caller).await?;
79 let board = lookup_board(self, &space, board_id, caller).await?;
80 let _ = lookup_issue(self, &board, issue_id).await?;
74 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
8175
8276 let existing = issue_watch::Entity::find()
8377 .filter(issue_watch::Column::IssueId.eq(Some(issue_id)))
@@ -126,10 +120,7 @@
126120 board_id: Uuid,
127121 issue_id: Uuid,
128122 ) -> Result<(), IssueError> {
129 let space = lookup_space_by_name(self, space_name).await?;
130 require_member(self, &space, caller).await?;
131 let board = lookup_board(self, &space, board_id, caller).await?;
132 let _ = lookup_issue(self, &board, issue_id).await?;
123 require_issue_access(self, caller, space_name, board_id, issue_id).await?;
133124
134125 let res = issue_watch::Entity::delete_many()
135126 .filter(issue_watch::Column::IssueId.eq(Some(issue_id)))
Modified lib/issues/src/share/lookup.rs View lib/issues/src/share/lookup.rs
@@ -106,3 +106,47 @@
106106 Err(IssueError::Forbidden)
107107 }
108108}
109
110pub async fn space_context(
111 service: &IssueService,
112 caller: Uuid,
113 space_name: &str,
114) -> Result<spaces::Model, IssueError> {
115 let space = lookup_space_by_name(service, space_name).await?;
116 require_member(service, &space, caller).await?;
117 Ok(space)
118}
119
120pub async fn board_context(
121 service: &IssueService,
122 caller: Uuid,
123 space_name: &str,
124 board_id: Uuid,
125) -> Result<(spaces::Model, issues::Model), IssueError> {
126 let space = space_context(service, caller, space_name).await?;
127 let board = lookup_board(service, &space, board_id, caller).await?;
128 Ok((space, board))
129}
130
131pub async fn issue_context(
132 service: &IssueService,
133 caller: Uuid,
134 space_name: &str,
135 board_id: Uuid,
136 issue_id: Uuid,
137) -> Result<(spaces::Model, issues::Model, issue_item::Model), IssueError> {
138 let (space, board) = board_context(service, caller, space_name, board_id).await?;
139 let item = lookup_issue(service, &board, issue_id).await?;
140 Ok((space, board, item))
141}
142
143pub async fn require_issue_access(
144 service: &IssueService,
145 caller: Uuid,
146 space_name: &str,
147 board_id: Uuid,
148 issue_id: Uuid,
149) -> Result<(), IssueError> {
150 issue_context(service, caller, space_name, board_id, issue_id).await?;
151 Ok(())
152}
Modified Cargo.lock View Cargo.lock
@@ -9041,7 +9041,7 @@
90419041[[package]]
90429042name = "web"
90439043version = "0.1.0"
9044source = "git+https://gitea.gitdata.me/GitDataAi/web.git?rev=67436b2ad9#67436b2ad97b6d0ccc548cb3b1d5691711a01214"
9044source = "git+https://gitea.gitdata.me/GitDataAi/web.git?rev=cd8cdac92ffbf482b5318f93848f82ed4140b7a6#cd8cdac92ffbf482b5318f93848f82ed4140b7a6"
90459045
90469046[[package]]
90479047name = "web-sys"
Modified openapi.json View openapi.json

Diff too large to display.