refactor: refresh lib/issues (features, handler, tests) and lib/auth to align with current schema
zhenyi committed
Modified lib/auth/features/login/impls.rs lib/auth/features/login/impls.rs +5 −1 View lib/auth/features/login/impls.rs
@@ -1,4 +1,4 @@
1use super::types::{LoginCmd, LoginResp};
1use super::types::{LoginCmd, LoginResp, TwoFactorLoginCmd};
22use super::usecase;
33use crate::error::AuthError;
44use crate::features::AuthService;
@@ -11,4 +11,8 @@
1111 ) -> Result<LoginResp, AuthError> {
1212 usecase::login(cmd, self, expected_cid.as_deref()).await
1313 }
14
15 pub async fn login_2fa(&self, cmd: TwoFactorLoginCmd) -> Result<LoginResp, AuthError> {
16 usecase::login_2fa(cmd, self).await
17 }
1418}
Modified lib/auth/features/login/types.rs lib/auth/features/login/types.rs +10 −0 View lib/auth/features/login/types.rs
@@ -76,4 +76,14 @@
7676 pub token_type: String,
7777 pub expires_in: i64,
7878 pub user: UserPublic,
79 #[serde(default)]
80 pub needs_2fa: bool,
81 #[serde(default)]
82 pub challenge: Option<String>,
83}
84
85#[derive(Debug, Clone, Deserialize, ToSchema)]
86pub struct TwoFactorLoginCmd {
87 pub challenge: String,
88 pub code: String,
7989}
Modified lib/auth/features/login/usecase.rs lib/auth/features/login/usecase.rs +115 −1 View lib/auth/features/login/usecase.rs
@@ -1,6 +1,8 @@
11use jwt::handlers::TokenPairResponse;
22use jwt::rotation::{RotationContext, issue_login_pair};
33use time::OffsetDateTime;
4use totp::{self, TotpError};
5use uuid::Uuid;
46
57use db::conn::sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, QueryFilter, Set};
68use db::user::user_emails::Entity as EmailEntity;
@@ -13,7 +15,9 @@
1315use crate::metrics::{self, JwtPairKind};
1416use crate::pow::PowService;
1517
16use super::types::{LoginCmd, LoginResp, UserPublic};
18use super::types::{LoginCmd, LoginResp, TwoFactorLoginCmd, UserPublic};
19
20const TWO_FACTOR_CHALLENGE_TTL_SECS: u64 = 300;
1721
1822async fn verify_pow(
1923 pow: &PowService,
@@ -63,9 +67,31 @@
6367 token_type: pair.token_type,
6468 expires_in: pair.expires_in,
6569 user: UserPublic::from(user),
70 needs_2fa: false,
71 challenge: None,
6672 }
6773}
6874
75fn pending_2fa_resp(user: db::user::users::Model, challenge: String) -> LoginResp {
76 LoginResp {
77 access_token: String::new(),
78 refresh_token: String::new(),
79 token_type: String::new(),
80 expires_in: 0,
81 user: UserPublic::from(user),
82 needs_2fa: true,
83 challenge: Some(challenge),
84 }
85}
86
87fn map_totp(e: TotpError) -> AuthError {
88 match e {
89 TotpError::InvalidCode => AuthError::BadRequest("invalid code".into()),
90 TotpError::InvalidOrExpiredCode => AuthError::BadRequest("invalid or expired code".into()),
91 _ => AuthError::Internal(e.to_string()),
92 }
93}
94
6995pub async fn login(
7096 cmd: LoginCmd,
7197 svc: &AuthService,
@@ -109,6 +135,19 @@
109135 return Err(AuthError::InvalidCredentials);
110136 }
111137
138 if user.totp_enabled {
139 let challenge = Uuid::now_v7().to_string();
140 let key = format!("auth:2fa:{challenge}");
141 svc.kv
142 .set_bytes(
143 &key,
144 user.uid.to_string().into_bytes(),
145 TWO_FACTOR_CHALLENGE_TTL_SECS,
146 )
147 .await?;
148 return Ok(pending_2fa_resp(user, challenge));
149 }
150
112151 let cache = svc.jwt.cache.read().await;
113152 let ctx = RotationContext {
114153 db: &db,
@@ -135,6 +174,81 @@
135174 }
136175 .await;
137176 metrics::record_outcome(metrics::FEATURE_LOGIN, &result);
177 if result.is_ok() && !result.as_ref().map(|r| r.needs_2fa).unwrap_or(false) {
178 metrics::record_jwt_pair_issued(JwtPairKind::Login);
179 }
180 result
181}
182
183pub async fn login_2fa(
184 cmd: TwoFactorLoginCmd,
185 svc: &AuthService,
186) -> Result<LoginResp, AuthError> {
187 let _timer = metrics::Timer::start(metrics::FEATURE_LOGIN_2FA);
188 let result: Result<LoginResp, AuthError> = async {
189 let key = format!("auth:2fa:{}", cmd.challenge.trim());
190 let uid_raw = svc
191 .kv
192 .getdel_bytes(&key)
193 .await?
194 .ok_or_else(|| AuthError::Unauthorized("invalid or expired challenge".into()))?;
195 let uid_text = std::str::from_utf8(&uid_raw)
196 .map_err(|_| AuthError::Unauthorized("invalid or expired challenge".into()))?;
197 let uid = Uuid::parse_str(uid_text)
198 .map_err(|_| AuthError::Unauthorized("invalid or expired challenge".into()))?;
199
200 let db = svc.db.writer();
201 let user = UserEntity::find_by_id(uid)
202 .one(&db)
203 .await?
204 .ok_or_else(|| AuthError::Unauthorized("invalid or expired challenge".into()))?;
205
206 if !user.totp_enabled {
207 return Err(AuthError::Unauthorized("two-factor not enabled".into()));
208 }
209
210 let key_material = svc
211 .config
212 .totp_encryption_key
213 .as_deref()
214 .ok_or_else(|| AuthError::Internal("totp encryption key not configured".into()))?;
215 let stored = user
216 .totp_encrypted_secret
217 .clone()
218 .ok_or_else(|| AuthError::Internal("stored totp secret missing".into()))?;
219 let secret = totp::decrypt_secret(key_material, &stored).map_err(map_totp)?;
220 let (_, counter) =
221 totp::verify_totp_code(&secret, &cmd.code, user.totp_last_counter).map_err(map_totp)?;
222
223 let cache = svc.jwt.cache.read().await;
224 let ctx = RotationContext {
225 db: &db,
226 settings: &svc.jwt.settings,
227 cache: &cache,
228 };
229 let pair = issue_login_pair(
230 &ctx,
231 user.uid,
232 user.username.clone(),
233 user.is_admin,
234 user.is_restricted,
235 user.passwd_version.unwrap_or(0),
236 None,
237 )
238 .await?;
239
240 let now = OffsetDateTime::now_utc();
241 let mut am: UserActiveModel = user.clone().into();
242 am.last_login_at = Set(Some(now));
243 am.totp_last_used_at = Set(Some(now));
244 am.totp_last_counter = Set(Some(counter));
245 am.updated_at = Set(now);
246 let _ = am.update(&db).await;
247
248 Ok(build_resp(pair.into(), user))
249 }
250 .await;
251 metrics::record_outcome(metrics::FEATURE_LOGIN_2FA, &result);
138252 if result.is_ok() {
139253 metrics::record_jwt_pair_issued(JwtPairKind::Login);
140254 }
Modified lib/auth/handler/mod.rs lib/auth/handler/mod.rs +1 −0 View lib/auth/handler/mod.rs
@@ -3,6 +3,7 @@
33pub(crate) mod cookies;
44pub(crate) mod email;
55pub(crate) mod login;
6pub(crate) mod login_2fa;
67pub(crate) mod logout;
78pub(crate) mod me;
89pub(crate) mod middleware;
Modified lib/auth/handler/openapi.rs lib/auth/handler/openapi.rs +3 −1 View lib/auth/handler/openapi.rs
@@ -3,7 +3,7 @@
33use crate::features::TempKey;
44use crate::features::email_code::types::{VerifyCodeCmd, VerifyCodeResult};
55use crate::features::forgot_password::types::{ForgotPasswordCmd, ForgotPasswordResp};
6use crate::features::login::types::{LoginCmd, LoginResp, UserMe, UserPublic};
6use crate::features::login::types::{LoginCmd, LoginResp, TwoFactorLoginCmd, UserMe, UserPublic};
77use crate::features::oauth::types::{
88 AuthorizationUrlResp, AuthorizeCmd, CallbackCmd, OAuthLoginResp, OAuthProvider,
99 OAuthProviderInfo,
@@ -33,6 +33,7 @@
3333 paths(
3434 crate::handler::register::handler,
3535 crate::handler::login::handler,
36 crate::handler::login_2fa::handler,
3637 crate::handler::pow::handler,
3738 crate::handler::logout::handler,
3839 crate::handler::refresh::handler,
@@ -57,6 +58,7 @@
5758 RegisterResp,
5859 LoginCmd,
5960 LoginResp,
61 TwoFactorLoginCmd,
6062 LogoutCmd,
6163 LogoutResp,
6264 RefreshCmd,
Modified lib/auth/handler/routes.rs lib/auth/handler/routes.rs +1 −0 View lib/auth/handler/routes.rs
@@ -10,6 +10,7 @@
1010 let auth = Router::new()
1111 .route("/register", post(crate::handler::register::handler))
1212 .route("/login", post(crate::handler::login::handler))
13 .route("/login/2fa", post(crate::handler::login_2fa::handler))
1314 .route("/logout", post(crate::handler::logout::handler))
1415 .route("/refresh", post(crate::handler::refresh::handler))
1516 .route("/pow/challenge", get(crate::handler::pow::handler))
Modified lib/auth/Cargo.toml lib/auth/Cargo.toml +1 −0 View lib/auth/Cargo.toml
@@ -19,6 +19,7 @@
1919queue.workspace = true
2020jwt.workspace = true
2121rate_limit.workspace = true
22totp.workspace = true
2223tokio.workspace = true
2324axum.workspace = true
2425axum-extra.workspace = true
Modified lib/auth/metrics.rs lib/auth/metrics.rs +1 −0 View lib/auth/metrics.rs
@@ -5,6 +5,7 @@
55use crate::error::AuthError;
66
77pub const FEATURE_LOGIN: &str = "login";
8pub const FEATURE_LOGIN_2FA: &str = "login_2fa";
89pub const FEATURE_REGISTER: &str = "register";
910pub const FEATURE_REFRESH: &str = "refresh";
1011pub const FEATURE_LOGOUT: &str = "logout";
Modified lib/issues/features/lookup.rs lib/issues/features/lookup.rs +26 −0 View lib/issues/features/lookup.rs
@@ -36,3 +36,29 @@
3636 .ok_or_else(|| IssueError::NotFound(format!("issues board for space {space_id}")))?;
3737 Ok(board.channel_id)
3838}
39
40/// Resolve the channel of a specific board (issues collection) inside a
41/// space, validating that the board belongs to that space.
42///
43/// Used by issue-level handlers under
44/// `/issues/{space_name}/{issues_id}/**` so a board id from the wire is
45/// cross-checked against the space before use.
46pub async fn channel_for_collection(
47 svc: &IssueService,
48 space_id: Uuid,
49 collection_id: Uuid,
50) -> IssueResult<Uuid> {
51 let db = svc.db.reader();
52 let board = issue_entity::Entity::find()
53 .inner_join(channel_entity::Entity)
54 .filter(channel_entity::Column::SpaceId.eq(space_id))
55 .filter(issue_entity::Column::Id.eq(collection_id))
56 .one(&db)
57 .await?
58 .ok_or_else(|| {
59 IssueError::NotFound(format!(
60 "issues board {collection_id} for space {space_id}"
61 ))
62 })?;
63 Ok(board.channel_id)
64}
Modified lib/issues/handler/comment/create.rs lib/issues/handler/comment/create.rs +3 −2 View lib/issues/handler/comment/create.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{issue_id}/comments",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/comments",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 request_body = AppRequestData<CreateCommentOption>,
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 input: AppRequest<CreateCommentOption>,
2728) -> IssueResult<AppResponseData<CommentView>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/comment/delete.rs lib/issues/handler/comment/delete.rs +3 −2 View lib/issues/handler/comment/delete.rs
@@ -8,10 +8,11 @@
88
99#[utoipa::path(
1010 delete,
11 path = "/issues/{space_name}/{issue_id}/comments/{comment_id}",
11 path = "/issues/{space_name}/{issues_id}/{issue_id}/comments/{comment_id}",
1212 tag = "issues",
1313 params(
1414 ("space_name" = String, Path),
15 ("issues_id" = Uuid, Path),
1516 ("issue_id" = Uuid, Path),
1617 ("comment_id" = Uuid, Path),
1718 ),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id, comment_id)): Path<(String, Uuid, Uuid)>,
25 Path((space_name, _issues_id, issue_id, comment_id)): Path<(String, Uuid, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<NoData>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/comment/get.rs lib/issues/handler/comment/get.rs +3 −2 View lib/issues/handler/comment/get.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/comments/{comment_id}",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/comments/{comment_id}",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ("comment_id" = Uuid, Path),
1819 ),
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id, comment_id)): Path<(String, Uuid, Uuid)>,
26 Path((space_name, _issues_id, issue_id, comment_id)): Path<(String, Uuid, Uuid, Uuid)>,
2627) -> IssueResult<AppResponseData<CommentView>> {
2728 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2829 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/comment/list.rs lib/issues/handler/comment/list.rs +3 −2 View lib/issues/handler/comment/list.rs
@@ -9,11 +9,12 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/comments",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/comments",
1313 tag = "issues",
1414 params(
1515 ListCommentQuery,
1616 ("space_name" = String, Path),
17 ("issues_id" = Uuid, Path),
1718 ("issue_id" = Uuid, Path),
1819 ),
1920 responses((status = 200, description = "List comments")),
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 Query(q): Query<ListCommentQuery>,
2728) -> IssueResult<AppResponseData<serde_json::Value>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/comment/update.rs lib/issues/handler/comment/update.rs +3 −2 View lib/issues/handler/comment/update.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 patch,
12 path = "/issues/{space_name}/{issue_id}/comments/{comment_id}",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/comments/{comment_id}",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ("comment_id" = Uuid, Path),
1819 ),
@@ -23,7 +24,7 @@
2324pub(crate) async fn handler(
2425 State(svc): State<IssueService>,
2526 AuthUser { claims }: AuthUser,
26 Path((space_name, issue_id, comment_id)): Path<(String, Uuid, Uuid)>,
27 Path((space_name, _issues_id, issue_id, comment_id)): Path<(String, Uuid, Uuid, Uuid)>,
2728 input: AppRequest<UpdateCommentOption>,
2829) -> IssueResult<AppResponseData<CommentView>> {
2930 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue/close.rs lib/issues/handler/issue/close.rs +3 −2 View lib/issues/handler/issue/close.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{issue_id}/close",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/close",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 request_body = AppRequestData<CloseIssueOption>,
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 input: AppRequest<CloseIssueOption>,
2728) -> IssueResult<AppResponseData<IssueView>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue/create.rs lib/issues/handler/issue/create.rs +7 −4 View lib/issues/handler/issue/create.rs
@@ -1,6 +1,7 @@
11use axum::extract::{Path, State};
22use export::data::{AppRequest, AppRequestData, AppResponseData};
33use jwt::extractor::AuthUser;
4use uuid::Uuid;
45
56use crate::error::IssueResult;
67use crate::features::IssueService;
@@ -8,24 +9,26 @@
89
910#[utoipa::path(
1011 post,
11 path = "/issues/{space_name}",
12 path = "/issues/{space_name}/{issues_id}",
1213 tag = "issues",
1314 params(
1415 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path, description = "Board (issues collection) id"),
1517 ),
1618 request_body = AppRequestData<CreateIssueOption>,
17 responses((status = 201, description = "Create an issue in this channel's board", body = AppResponseData<IssueView>)),
19 responses((status = 201, description = "Create an issue in this board", body = AppResponseData<IssueView>)),
1820 security(("bearer_auth" = []))
1921)]
2022pub(crate) async fn handler(
2123 State(svc): State<IssueService>,
2224 AuthUser { claims }: AuthUser,
23 Path(space_name): Path<String>,
25 Path((space_name, issues_id)): Path<(String, Uuid)>,
2426 input: AppRequest<CreateIssueOption>,
2527) -> IssueResult<AppResponseData<IssueView>> {
2628 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2729 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
28 let channel_id = crate::features::lookup::channel_for_space(&svc, space_id).await?;
30 let channel_id =
31 crate::features::lookup::channel_for_collection(&svc, space_id, issues_id).await?;
2932 let view = svc
3033 .create_issue(claims.sub, channel_id, input.into_inner())
3134 .await?;
Modified lib/issues/handler/issue/delete.rs lib/issues/handler/issue/delete.rs +3 −2 View lib/issues/handler/issue/delete.rs
@@ -8,10 +8,11 @@
88
99#[utoipa::path(
1010 delete,
11 path = "/issues/{space_name}/{issue_id}",
11 path = "/issues/{space_name}/{issues_id}/{issue_id}",
1212 tag = "issues",
1313 params(
1414 ("space_name" = String, Path),
15 ("issues_id" = Uuid, Path),
1516 ("issue_id" = Uuid, Path),
1617 ),
1718 responses((status = 204, description = "Delete issue")),
@@ -20,7 +21,7 @@
2021pub(crate) async fn handler(
2122 State(svc): State<IssueService>,
2223 AuthUser { claims }: AuthUser,
23 Path((space_name, issue_id)): Path<(String, Uuid)>,
24 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2425) -> IssueResult<AppResponseData<NoData>> {
2526 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2627 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue/get.rs lib/issues/handler/issue/get.rs +3 −2 View lib/issues/handler/issue/get.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 responses((status = 200, description = "Get issue", body = AppResponseData<IssueView>)),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id)): Path<(String, Uuid)>,
25 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<IssueView>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue/mod.rs lib/issues/handler/issue/mod.rs +1 −0 View lib/issues/handler/issue/mod.rs
@@ -2,5 +2,6 @@
22pub(crate) mod create;
33pub(crate) mod delete;
44pub(crate) mod get;
5pub(crate) mod list;
56pub(crate) mod reopen;
67pub(crate) mod update;
Modified lib/issues/handler/issue/reopen.rs lib/issues/handler/issue/reopen.rs +4 −3 View lib/issues/handler/issue/reopen.rs
@@ -1,4 +1,4 @@
1//! POST /issues/{space_name}/{issue_id}/reopen
1//! POST /issues/{space_name}/{issues_id}/{issue_id}/reopen
22//!
33//! Layout: Issue detail — sidebar "Reopen issue" button (design doc
44//! wiki/issues/design.md §3.2). Reuses the existing close_issue usecase
@@ -15,10 +15,11 @@
1515
1616#[utoipa::path(
1717 post,
18 path = "/issues/{space_name}/{issue_id}/reopen",
18 path = "/issues/{space_name}/{issues_id}/{issue_id}/reopen",
1919 tag = "issues",
2020 params(
2121 ("space_name" = String, Path),
22 ("issues_id" = Uuid, Path),
2223 ("issue_id" = Uuid, Path),
2324 ),
2425 responses((status = 200, description = "Reopen a closed issue", body = AppResponseData<IssueView>)),
@@ -27,7 +28,7 @@
2728pub(crate) async fn handler(
2829 State(svc): State<IssueService>,
2930 AuthUser { claims }: AuthUser,
30 Path((space_name, issue_id)): Path<(String, Uuid)>,
31 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
3132) -> IssueResult<AppResponseData<IssueView>> {
3233 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
3334 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue/update.rs lib/issues/handler/issue/update.rs +3 −2 View lib/issues/handler/issue/update.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 patch,
12 path = "/issues/{space_name}/{issue_id}",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 request_body = AppRequestData<UpdateIssueOption>,
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 input: AppRequest<UpdateIssueOption>,
2728) -> IssueResult<AppResponseData<IssueView>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue_content_history/list.rs lib/issues/handler/issue_content_history/list.rs +3 −2 View lib/issues/handler/issue_content_history/list.rs
@@ -9,11 +9,12 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/content_history",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/content_history",
1313 tag = "issues",
1414 params(
1515 ListContentHistoryQuery,
1616 ("space_name" = String, Path),
17 ("issues_id" = Uuid, Path),
1718 ("issue_id" = Uuid, Path),
1819 ),
1920 responses((status = 200, description = "List content history")),
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 Query(q): Query<ListContentHistoryQuery>,
2728) -> IssueResult<AppResponseData<serde_json::Value>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue_dependency/create.rs lib/issues/handler/issue_dependency/create.rs +3 −2 View lib/issues/handler/issue_dependency/create.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{issue_id}/dependencies",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/dependencies",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 request_body = AppRequestData<CreateDependencyOption>,
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 input: AppRequest<CreateDependencyOption>,
2728) -> IssueResult<AppResponseData<DependencyView>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue_dependency/delete.rs lib/issues/handler/issue_dependency/delete.rs +3 −2 View lib/issues/handler/issue_dependency/delete.rs
@@ -8,10 +8,11 @@
88
99#[utoipa::path(
1010 delete,
11 path = "/issues/{space_name}/{issue_id}/dependencies/{dep_id}",
11 path = "/issues/{space_name}/{issues_id}/{issue_id}/dependencies/{dep_id}",
1212 tag = "issues",
1313 params(
1414 ("space_name" = String, Path),
15 ("issues_id" = Uuid, Path),
1516 ("issue_id" = Uuid, Path),
1617 ("dep_id" = Uuid, Path),
1718 ),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id, dep_id)): Path<(String, Uuid, Uuid)>,
25 Path((space_name, _issues_id, issue_id, dep_id)): Path<(String, Uuid, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<NoData>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_dependency/list.rs lib/issues/handler/issue_dependency/list.rs +3 −2 View lib/issues/handler/issue_dependency/list.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/dependencies",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/dependencies",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 responses((status = 200, description = "List dependencies")),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id)): Path<(String, Uuid)>,
25 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<Vec<DependencyView>>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_event/list.rs lib/issues/handler/issue_event/list.rs +3 −2 View lib/issues/handler/issue_event/list.rs
@@ -9,11 +9,12 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/events",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/events",
1313 tag = "issues",
1414 params(
1515 ListEventQuery,
1616 ("space_name" = String, Path),
17 ("issues_id" = Uuid, Path),
1718 ("issue_id" = Uuid, Path),
1819 ),
1920 responses((status = 200, description = "List issue events")),
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 Query(q): Query<ListEventQuery>,
2728) -> IssueResult<AppResponseData<serde_json::Value>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue_member/create.rs lib/issues/handler/issue_member/create.rs +3 −2 View lib/issues/handler/issue_member/create.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{issue_id}/members",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/members",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 request_body = AppRequestData<CreateMemberOption>,
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 input: AppRequest<CreateMemberOption>,
2728) -> IssueResult<AppResponseData<MemberView>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue_member/delete.rs lib/issues/handler/issue_member/delete.rs +3 −2 View lib/issues/handler/issue_member/delete.rs
@@ -8,10 +8,11 @@
88
99#[utoipa::path(
1010 delete,
11 path = "/issues/{space_name}/{issue_id}/members/{member_id}",
11 path = "/issues/{space_name}/{issues_id}/{issue_id}/members/{member_id}",
1212 tag = "issues",
1313 params(
1414 ("space_name" = String, Path),
15 ("issues_id" = Uuid, Path),
1516 ("issue_id" = Uuid, Path),
1617 ("member_id" = Uuid, Path),
1718 ),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id, member_id)): Path<(String, Uuid, Uuid)>,
25 Path((space_name, _issues_id, issue_id, member_id)): Path<(String, Uuid, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<NoData>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_member/list.rs lib/issues/handler/issue_member/list.rs +3 −2 View lib/issues/handler/issue_member/list.rs
@@ -9,11 +9,12 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/members",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/members",
1313 tag = "issues",
1414 params(
1515 ListMemberQuery,
1616 ("space_name" = String, Path),
17 ("issues_id" = Uuid, Path),
1718 ("issue_id" = Uuid, Path),
1819 ),
1920 responses((status = 200, description = "List members")),
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 Query(q): Query<ListMemberQuery>,
2728) -> IssueResult<AppResponseData<serde_json::Value>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue_reaction/create.rs lib/issues/handler/issue_reaction/create.rs +3 −2 View lib/issues/handler/issue_reaction/create.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{issue_id}/reactions",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/reactions",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 request_body = AppRequestData<CreateReactionOption>,
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 input: AppRequest<CreateReactionOption>,
2728) -> IssueResult<AppResponseData<ReactionView>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue_reaction/create_comment.rs lib/issues/handler/issue_reaction/create_comment.rs +3 −2 View lib/issues/handler/issue_reaction/create_comment.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{issue_id}/comments/{comment_id}/reactions",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/comments/{comment_id}/reactions",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ("comment_id" = Uuid, Path),
1819 ),
@@ -23,7 +24,7 @@
2324pub(crate) async fn handler(
2425 State(svc): State<IssueService>,
2526 AuthUser { claims }: AuthUser,
26 Path((space_name, issue_id, comment_id)): Path<(String, Uuid, Uuid)>,
27 Path((space_name, _issues_id, issue_id, comment_id)): Path<(String, Uuid, Uuid, Uuid)>,
2728 input: AppRequest<CreateReactionOption>,
2829) -> IssueResult<AppResponseData<ReactionView>> {
2930 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue_reaction/delete.rs lib/issues/handler/issue_reaction/delete.rs +3 −2 View lib/issues/handler/issue_reaction/delete.rs
@@ -8,10 +8,11 @@
88
99#[utoipa::path(
1010 delete,
11 path = "/issues/{space_name}/{issue_id}/reactions/{reaction_id}",
11 path = "/issues/{space_name}/{issues_id}/{issue_id}/reactions/{reaction_id}",
1212 tag = "issues",
1313 params(
1414 ("space_name" = String, Path),
15 ("issues_id" = Uuid, Path),
1516 ("issue_id" = Uuid, Path),
1617 ("reaction_id" = Uuid, Path),
1718 ),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id, reaction_id)): Path<(String, Uuid, Uuid)>,
25 Path((space_name, _issues_id, issue_id, reaction_id)): Path<(String, Uuid, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<NoData>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_reaction/list.rs lib/issues/handler/issue_reaction/list.rs +3 −2 View lib/issues/handler/issue_reaction/list.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/reactions",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/reactions",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 responses((status = 200, description = "List issue reactions")),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id)): Path<(String, Uuid)>,
25 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<Vec<ReactionAggView>>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_reaction/list_comment.rs lib/issues/handler/issue_reaction/list_comment.rs +3 −2 View lib/issues/handler/issue_reaction/list_comment.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/comments/{comment_id}/reactions",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/comments/{comment_id}/reactions",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ("comment_id" = Uuid, Path),
1819 ),
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id, comment_id)): Path<(String, Uuid, Uuid)>,
26 Path((space_name, _issues_id, issue_id, comment_id)): Path<(String, Uuid, Uuid, Uuid)>,
2627) -> IssueResult<AppResponseData<Vec<ReactionAggView>>> {
2728 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2829 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_repo_rel/create.rs lib/issues/handler/issue_repo_rel/create.rs +3 −2 View lib/issues/handler/issue_repo_rel/create.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{issue_id}/repo_relations",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/repo_relations",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 request_body = AppRequestData<CreateRepoRelOption>,
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 input: AppRequest<CreateRepoRelOption>,
2728) -> IssueResult<AppResponseData<RepoRelView>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue_repo_rel/delete.rs lib/issues/handler/issue_repo_rel/delete.rs +3 −2 View lib/issues/handler/issue_repo_rel/delete.rs
@@ -8,10 +8,11 @@
88
99#[utoipa::path(
1010 delete,
11 path = "/issues/{space_name}/{issue_id}/repo_relations/{rel_id}",
11 path = "/issues/{space_name}/{issues_id}/{issue_id}/repo_relations/{rel_id}",
1212 tag = "issues",
1313 params(
1414 ("space_name" = String, Path),
15 ("issues_id" = Uuid, Path),
1516 ("issue_id" = Uuid, Path),
1617 ("rel_id" = Uuid, Path),
1718 ),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id, rel_id)): Path<(String, Uuid, Uuid)>,
25 Path((space_name, _issues_id, issue_id, rel_id)): Path<(String, Uuid, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<NoData>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_repo_rel/list.rs lib/issues/handler/issue_repo_rel/list.rs +3 −2 View lib/issues/handler/issue_repo_rel/list.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/repo_relations",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/repo_relations",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 responses((status = 200, description = "List repo relations")),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id)): Path<(String, Uuid)>,
25 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<Vec<RepoRelView>>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_stopwatch/get.rs lib/issues/handler/issue_stopwatch/get.rs +3 −2 View lib/issues/handler/issue_stopwatch/get.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/stopwatch",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/stopwatch",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 responses((status = 200, description = "Get my stopwatch", body = AppResponseData<StopwatchView>)),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id)): Path<(String, Uuid)>,
25 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<StopwatchView>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_stopwatch/start.rs lib/issues/handler/issue_stopwatch/start.rs +3 −2 View lib/issues/handler/issue_stopwatch/start.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{issue_id}/stopwatch",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/stopwatch",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 responses((status = 201, description = "Start stopwatch", body = AppResponseData<StopwatchView>)),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id)): Path<(String, Uuid)>,
25 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<StopwatchView>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_stopwatch/stop.rs lib/issues/handler/issue_stopwatch/stop.rs +3 −2 View lib/issues/handler/issue_stopwatch/stop.rs
@@ -8,10 +8,11 @@
88
99#[utoipa::path(
1010 delete,
11 path = "/issues/{space_name}/{issue_id}/stopwatch",
11 path = "/issues/{space_name}/{issues_id}/{issue_id}/stopwatch",
1212 tag = "issues",
1313 params(
1414 ("space_name" = String, Path),
15 ("issues_id" = Uuid, Path),
1516 ("issue_id" = Uuid, Path),
1617 ),
1718 responses((status = 204, description = "Stop stopwatch")),
@@ -20,7 +21,7 @@
2021pub(crate) async fn handler(
2122 State(svc): State<IssueService>,
2223 AuthUser { claims }: AuthUser,
23 Path((space_name, issue_id)): Path<(String, Uuid)>,
24 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2425) -> IssueResult<AppResponseData<NoData>> {
2526 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2627 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_tracked_time/list.rs lib/issues/handler/issue_tracked_time/list.rs +3 −2 View lib/issues/handler/issue_tracked_time/list.rs
@@ -9,11 +9,12 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/tracked_time",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/tracked_time",
1313 tag = "issues",
1414 params(
1515 ListTrackedTimeQuery,
1616 ("space_name" = String, Path),
17 ("issues_id" = Uuid, Path),
1718 ("issue_id" = Uuid, Path),
1819 ),
1920 responses((status = 200, description = "List tracked time")),
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 Query(q): Query<ListTrackedTimeQuery>,
2728) -> IssueResult<AppResponseData<serde_json::Value>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue_user/list.rs lib/issues/handler/issue_user/list.rs +3 −2 View lib/issues/handler/issue_user/list.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/users",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/users",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 responses((status = 200, description = "List issue users")),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id)): Path<(String, Uuid)>,
25 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<Vec<IssueUserView>>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_user/update.rs lib/issues/handler/issue_user/update.rs +3 −2 View lib/issues/handler/issue_user/update.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 patch,
12 path = "/issues/{space_name}/{issue_id}/users/{uid}",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/users/{uid}",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ("uid" = Uuid, Path),
1819 ),
@@ -23,7 +24,7 @@
2324pub(crate) async fn handler(
2425 State(svc): State<IssueService>,
2526 AuthUser { claims }: AuthUser,
26 Path((space_name, issue_id, uid)): Path<(String, Uuid, Uuid)>,
27 Path((space_name, _issues_id, issue_id, uid)): Path<(String, Uuid, Uuid, Uuid)>,
2728 input: AppRequest<UpdateIssueUserOption>,
2829) -> IssueResult<AppResponseData<IssueUserView>> {
2930 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issue_watch/delete.rs lib/issues/handler/issue_watch/delete.rs +3 −2 View lib/issues/handler/issue_watch/delete.rs
@@ -8,10 +8,11 @@
88
99#[utoipa::path(
1010 delete,
11 path = "/issues/{space_name}/{issue_id}/watch",
11 path = "/issues/{space_name}/{issues_id}/{issue_id}/watch",
1212 tag = "issues",
1313 params(
1414 ("space_name" = String, Path),
15 ("issues_id" = Uuid, Path),
1516 ("issue_id" = Uuid, Path),
1617 ),
1718 responses((status = 204, description = "Delete watch state")),
@@ -20,7 +21,7 @@
2021pub(crate) async fn handler(
2122 State(svc): State<IssueService>,
2223 AuthUser { claims }: AuthUser,
23 Path((space_name, issue_id)): Path<(String, Uuid)>,
24 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2425) -> IssueResult<AppResponseData<NoData>> {
2526 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2627 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_watch/get.rs lib/issues/handler/issue_watch/get.rs +3 −2 View lib/issues/handler/issue_watch/get.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/watch",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/watch",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 responses((status = 200, description = "Get my watch state", body = AppResponseData<WatchView>)),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id)): Path<(String, Uuid)>,
25 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<WatchView>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/issue_watch/upsert.rs lib/issues/handler/issue_watch/upsert.rs +3 −2 View lib/issues/handler/issue_watch/upsert.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 put,
12 path = "/issues/{space_name}/{issue_id}/watch",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/watch",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 request_body = AppRequestData<UpsertWatchOption>,
@@ -22,7 +23,7 @@
2223pub(crate) async fn handler(
2324 State(svc): State<IssueService>,
2425 AuthUser { claims }: AuthUser,
25 Path((space_name, issue_id)): Path<(String, Uuid)>,
26 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2627 input: AppRequest<UpsertWatchOption>,
2728) -> IssueResult<AppResponseData<WatchView>> {
2829 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
Modified lib/issues/handler/issues/create.rs lib/issues/handler/issues/create.rs +16 −6 View lib/issues/handler/issues/create.rs
@@ -1,15 +1,18 @@
1use axum::extract::State;
1use axum::extract::{Path, State};
22use export::data::{AppRequest, AppRequestData, AppResponseData};
33use jwt::extractor::AuthUser;
44
5use crate::error::IssueResult;
5use crate::error::{IssueError, IssueResult};
66use crate::features::IssueService;
77use crate::features::issues::{CollectionView, CreateCollectionOption};
88
99#[utoipa::path(
1010 post,
11 path = "/issues",
11 path = "/issues/{space_name}",
1212 tag = "issues",
13 params(
14 ("space_name" = String, Path),
15 ),
1316 request_body = AppRequestData<CreateCollectionOption>,
1417 responses((status = 201, description = "Create an issue board (collection)", body = AppResponseData<CollectionView>)),
1518 security(("bearer_auth" = []))
@@ -17,10 +20,17 @@
1720pub(crate) async fn handler(
1821 State(svc): State<IssueService>,
1922 AuthUser { claims }: AuthUser,
23 Path(space_name): Path<String>,
2024 input: AppRequest<CreateCollectionOption>,
2125) -> IssueResult<AppResponseData<CollectionView>> {
22 let view = svc
23 .create_collection(claims.sub, input.into_inner())
24 .await?;
26 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
27 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
28 let input = input.into_inner();
29 if input.space_id != space_id {
30 return Err(IssueError::BadRequest(
31 "space_id in body does not match the space in the path".to_string(),
32 ));
33 }
34 let view = svc.create_collection(claims.sub, input).await?;
2535 Ok(AppResponseData::ok(view).with_code(201))
2636}
Modified lib/issues/handler/issues/get.rs lib/issues/handler/issues/get.rs +6 −36 View lib/issues/handler/issues/get.rs
@@ -1,22 +1,10 @@
1use axum::extract::{Path, Query, State};
1use axum::extract::{Path, State};
22use export::data::AppResponseData;
33use jwt::extractor::AuthUser;
4use serde::Serialize;
5use serde_json::Value;
64
75use crate::error::IssueResult;
86use crate::features::IssueService;
9use crate::features::issue::ListIssueQuery;
10
11#[derive(Serialize)]
12#[serde(untagged)]
13enum GetChannelResponse {
14 Board(crate::features::issues::CollectionView),
15 IssueList {
16 items: Vec<crate::features::issue::IssueView>,
17 total: i64,
18 },
19}
7use crate::features::issues::CollectionView;
208
219#[utoipa::path(
2210 get,
@@ -25,35 +13,17 @@
2513 params(
2614 ("space_name" = String, Path),
2715 ),
28 responses((status = 200, description = "Default issue board OR issue list (when query.state/q/author_id/milestone_id is set)")),
16 responses((status = 200, description = "Default issue board of the space", body = AppResponseData<CollectionView>)),
2917 security(("bearer_auth" = []))
3018)]
3119pub(crate) async fn handler(
3220 State(svc): State<IssueService>,
3321 AuthUser { claims }: AuthUser,
3422 Path(space_name): Path<String>,
35 Query(q): Query<ListIssueQuery>,
36) -> IssueResult<AppResponseData<Value>> {
23) -> IssueResult<AppResponseData<CollectionView>> {
3724 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
3825 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
3926 let channel_id = crate::features::lookup::channel_for_space(&svc, space_id).await?;
40 let wants_issue_list =
41 q.state.is_some() || q.q.is_some() || q.author_id.is_some() || q.milestone_id.is_some();
42 if wants_issue_list {
43 let page = svc.list_issues(claims.sub, channel_id, q).await?;
44 let resp = GetChannelResponse::IssueList {
45 items: page.items,
46 total: page.total,
47 };
48 Ok(AppResponseData::ok(
49 serde_json::to_value(resp).unwrap_or(Value::Null),
50 ))
51 } else {
52 let board = svc.load_default_collection(channel_id).await?;
53 let resp = GetChannelResponse::Board(board);
54 let _ = claims;
55 Ok(AppResponseData::ok(
56 serde_json::to_value(resp).unwrap_or(Value::Null),
57 ))
58 }
27 let board = svc.load_default_collection(channel_id).await?;
28 Ok(AppResponseData::ok(board))
5929}
Deleted lib/issues/handler/issues/list.rs lib/issues/handler/issues/list.rs +0 −24
@@ -1,24 +0,0 @@
1use axum::extract::State;
2use export::data::AppResponseData;
3use jwt::extractor::AuthUser;
4
5use crate::error::IssueResult;
6use crate::features::IssueService;
7use crate::features::issues::{CollectionView, ListCollectionQuery};
8
9#[utoipa::path(
10 get,
11 path = "/issues",
12 tag = "issues",
13 params(ListCollectionQuery),
14 responses((status = 200, description = "List issue boards (collections)", body = AppResponseData<Vec<CollectionView>>)),
15 security(("bearer_auth" = []))
16)]
17pub(crate) async fn handler(
18 State(svc): State<IssueService>,
19 AuthUser { claims }: AuthUser,
20 axum::extract::Query(q): axum::extract::Query<ListCollectionQuery>,
21) -> IssueResult<AppResponseData<Vec<CollectionView>>> {
22 let page = svc.list_collections(claims.sub, q).await?;
23 Ok(AppResponseData::ok(page.items))
24}
Modified lib/issues/handler/issues/mod.rs lib/issues/handler/issues/mod.rs +0 −1 View lib/issues/handler/issues/mod.rs
@@ -1,5 +1,4 @@
11pub(crate) mod create;
22pub(crate) mod delete;
33pub(crate) mod get;
4pub(crate) mod list;
54pub(crate) mod update;
Modified lib/issues/handler/label/bind.rs lib/issues/handler/label/bind.rs +3 −2 View lib/issues/handler/label/bind.rs
@@ -8,10 +8,11 @@
88
99#[utoipa::path(
1010 post,
11 path = "/issues/{space_name}/{issue_id}/labels/{label_id}",
11 path = "/issues/{space_name}/{issues_id}/{issue_id}/labels/{label_id}",
1212 tag = "issues",
1313 params(
1414 ("space_name" = String, Path),
15 ("issues_id" = Uuid, Path),
1516 ("issue_id" = Uuid, Path),
1617 ("label_id" = Uuid, Path),
1718 ),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id, label_id)): Path<(String, Uuid, Uuid)>,
25 Path((space_name, _issues_id, issue_id, label_id)): Path<(String, Uuid, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<NoData>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/label/list_for_issue.rs lib/issues/handler/label/list_for_issue.rs +3 −2 View lib/issues/handler/label/list_for_issue.rs
@@ -9,10 +9,11 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{issue_id}/labels",
12 path = "/issues/{space_name}/{issues_id}/{issue_id}/labels",
1313 tag = "issues",
1414 params(
1515 ("space_name" = String, Path),
16 ("issues_id" = Uuid, Path),
1617 ("issue_id" = Uuid, Path),
1718 ),
1819 responses((status = 200, description = "List labels for issue")),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id)): Path<(String, Uuid)>,
25 Path((space_name, _issues_id, issue_id)): Path<(String, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<Vec<LabelView>>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/label/unbind.rs lib/issues/handler/label/unbind.rs +3 −2 View lib/issues/handler/label/unbind.rs
@@ -8,10 +8,11 @@
88
99#[utoipa::path(
1010 delete,
11 path = "/issues/{space_name}/{issue_id}/labels/{label_id}",
11 path = "/issues/{space_name}/{issues_id}/{issue_id}/labels/{label_id}",
1212 tag = "issues",
1313 params(
1414 ("space_name" = String, Path),
15 ("issues_id" = Uuid, Path),
1516 ("issue_id" = Uuid, Path),
1617 ("label_id" = Uuid, Path),
1718 ),
@@ -21,7 +22,7 @@
2122pub(crate) async fn handler(
2223 State(svc): State<IssueService>,
2324 AuthUser { claims }: AuthUser,
24 Path((space_name, issue_id, label_id)): Path<(String, Uuid, Uuid)>,
25 Path((space_name, _issues_id, issue_id, label_id)): Path<(String, Uuid, Uuid, Uuid)>,
2526) -> IssueResult<AppResponseData<NoData>> {
2627 let space_id = space::features::lookup::lookup_space_by_name(&svc, &space_name).await?;
2728 space::features::authz::require_active_member(&svc, claims.sub, space_id).await?;
Modified lib/issues/handler/openapi.rs lib/issues/handler/openapi.rs +1 −1 View lib/issues/handler/openapi.rs
@@ -27,12 +27,12 @@
2727 ),
2828 paths(
2929 crate::handler::issues::create::handler,
30 crate::handler::issues::list::handler,
3130 crate::handler::issues::get::handler,
3231 crate::handler::issues::update::handler,
3332 crate::handler::issues::delete::handler,
3433
3534 crate::handler::issue::create::handler,
35 crate::handler::issue::list::handler,
3636 crate::handler::issue::get::handler,
3737 crate::handler::issue::update::handler,
3838 crate::handler::issue::delete::handler,
Modified lib/issues/handler/routes.rs lib/issues/handler/routes.rs +29 −30 View lib/issues/handler/routes.rs
@@ -8,38 +8,37 @@
88
99pub fn router(svc: IssueService) -> Router {
1010 let protected: Router<IssueService> = Router::new()
11 .route("/issues", post(super::issues::create::handler))
12 .route("/issues", get(super::issues::list::handler))
13 .route("/issues/{space_name}", get(super::issues::get::handler))
1411 .route(
1512 "/issues/{space_name}",
16 patch(super::issues::update::handler),
13 get(super::issues::get::handler)
14 .post(super::issues::create::handler)
15 .patch(super::issues::update::handler)
16 .delete(super::issues::delete::handler),
1717 )
1818 .route(
19 "/issues/{space_name}",
20 delete(super::issues::delete::handler),
19 "/issues/{space_name}/{issues_id}",
20 get(super::issue::list::handler).post(super::issue::create::handler),
2121 )
22 .route("/issues/{space_name}", post(super::issue::create::handler))
2322 .route(
24 "/issues/{space_name}/{issue_id}",
23 "/issues/{space_name}/{issues_id}/{issue_id}",
2524 get(super::issue::get::handler)
2625 .patch(super::issue::update::handler)
2726 .delete(super::issue::delete::handler),
2827 )
2928 .route(
30 "/issues/{space_name}/{issue_id}/close",
29 "/issues/{space_name}/{issues_id}/{issue_id}/close",
3130 post(super::issue::close::handler),
3231 )
3332 .route(
34 "/issues/{space_name}/{issue_id}/reopen",
33 "/issues/{space_name}/{issues_id}/{issue_id}/reopen",
3534 post(super::issue::reopen::handler),
3635 )
3736 .route(
38 "/issues/{space_name}/{issue_id}/comments",
37 "/issues/{space_name}/{issues_id}/{issue_id}/comments",
3938 get(super::comment::list::handler).post(super::comment::create::handler),
4039 )
4140 .route(
42 "/issues/{space_name}/{issue_id}/comments/{comment_id}",
41 "/issues/{space_name}/{issues_id}/{issue_id}/comments/{comment_id}",
4342 get(super::comment::get::handler)
4443 .patch(super::comment::update::handler)
4544 .delete(super::comment::delete::handler),
@@ -49,11 +48,11 @@
4948 get(super::label::list::handler).post(super::label::create::handler),
5049 )
5150 .route(
52 "/issues/{space_name}/{issue_id}/labels",
51 "/issues/{space_name}/{issues_id}/{issue_id}/labels",
5352 get(super::label::list_for_issue::handler),
5453 )
5554 .route(
56 "/issues/{space_name}/{issue_id}/labels/{label_id}",
55 "/issues/{space_name}/{issues_id}/{issue_id}/labels/{label_id}",
5756 post(super::label::bind::handler).delete(super::label::unbind::handler),
5857 )
5958 .route(
@@ -73,30 +72,30 @@
7372 .delete(super::milestone::delete::handler),
7473 )
7574 .route(
76 "/issues/{space_name}/{issue_id}/reactions",
75 "/issues/{space_name}/{issues_id}/{issue_id}/reactions",
7776 post(super::issue_reaction::create::handler).get(super::issue_reaction::list::handler),
7877 )
7978 .route(
80 "/issues/{space_name}/{issue_id}/comments/{comment_id}/reactions",
79 "/issues/{space_name}/{issues_id}/{issue_id}/comments/{comment_id}/reactions",
8180 post(super::issue_reaction::create_comment::handler)
8281 .get(super::issue_reaction::list_comment::handler),
8382 )
8483 .route(
85 "/issues/{space_name}/{issue_id}/reactions/{reaction_id}",
84 "/issues/{space_name}/{issues_id}/{issue_id}/reactions/{reaction_id}",
8685 delete(super::issue_reaction::delete::handler),
8786 )
8887 .route(
89 "/issues/{space_name}/{issue_id}/watch",
88 "/issues/{space_name}/{issues_id}/{issue_id}/watch",
9089 get(super::issue_watch::get::handler)
9190 .put(super::issue_watch::upsert::handler)
9291 .delete(super::issue_watch::delete::handler),
9392 )
9493 .route(
95 "/issues/{space_name}/{issue_id}/members",
94 "/issues/{space_name}/{issues_id}/{issue_id}/members",
9695 get(super::issue_member::list::handler).post(super::issue_member::create::handler),
9796 )
9897 .route(
99 "/issues/{space_name}/{issue_id}/members/{member_id}",
98 "/issues/{space_name}/{issues_id}/{issue_id}/members/{member_id}",
10099 delete(super::issue_member::delete::handler),
101100 )
102101 .route(
@@ -108,46 +107,46 @@
108107 delete(super::issue_pin::delete::handler),
109108 )
110109 .route(
111 "/issues/{space_name}/{issue_id}/repo_relations",
110 "/issues/{space_name}/{issues_id}/{issue_id}/repo_relations",
112111 get(super::issue_repo_rel::list::handler).post(super::issue_repo_rel::create::handler),
113112 )
114113 .route(
115 "/issues/{space_name}/{issue_id}/repo_relations/{rel_id}",
114 "/issues/{space_name}/{issues_id}/{issue_id}/repo_relations/{rel_id}",
116115 delete(super::issue_repo_rel::delete::handler),
117116 )
118117 .route(
119 "/issues/{space_name}/{issue_id}/users",
118 "/issues/{space_name}/{issues_id}/{issue_id}/users",
120119 get(super::issue_user::list::handler),
121120 )
122121 .route(
123 "/issues/{space_name}/{issue_id}/users/{uid}",
122 "/issues/{space_name}/{issues_id}/{issue_id}/users/{uid}",
124123 patch(super::issue_user::update::handler),
125124 )
126125 .route(
127 "/issues/{space_name}/{issue_id}/events",
126 "/issues/{space_name}/{issues_id}/{issue_id}/events",
128127 get(super::issue_event::list::handler),
129128 )
130129 .route(
131 "/issues/{space_name}/{issue_id}/dependencies",
130 "/issues/{space_name}/{issues_id}/{issue_id}/dependencies",
132131 get(super::issue_dependency::list::handler)
133132 .post(super::issue_dependency::create::handler),
134133 )
135134 .route(
136 "/issues/{space_name}/{issue_id}/dependencies/{dep_id}",
135 "/issues/{space_name}/{issues_id}/{issue_id}/dependencies/{dep_id}",
137136 delete(super::issue_dependency::delete::handler),
138137 )
139138 .route(
140 "/issues/{space_name}/{issue_id}/content_history",
139 "/issues/{space_name}/{issues_id}/{issue_id}/content_history",
141140 get(super::issue_content_history::list::handler),
142141 )
143142 .route(
144 "/issues/{space_name}/{issue_id}/stopwatch",
143 "/issues/{space_name}/{issues_id}/{issue_id}/stopwatch",
145144 get(super::issue_stopwatch::get::handler)
146145 .post(super::issue_stopwatch::start::handler)
147146 .delete(super::issue_stopwatch::stop::handler),
148147 )
149148 .route(
150 "/issues/{space_name}/{issue_id}/tracked_time",
149 "/issues/{space_name}/{issues_id}/{issue_id}/tracked_time",
151150 get(super::issue_tracked_time::list::handler),
152151 )
153152 .route(
Modified lib/issues/tests/issues/get.rs lib/issues/tests/issues/get.rs +2 −2 View lib/issues/tests/issues/get.rs
@@ -1,6 +1,6 @@
1use super::{ISSUE_ID, SPACE_NAME, expect_unauthorized};
1use super::{ISSUES_ID, ISSUE_ID, SPACE_NAME, expect_unauthorized};
22
33#[tokio::test]
44async fn get_issue_requires_auth() {
5 expect_unauthorized(&format!("/issues/{SPACE_NAME}/{ISSUE_ID}"), "GET").await;
5 expect_unauthorized(&format!("/issues/{SPACE_NAME}/{ISSUES_ID}/{ISSUE_ID}"), "GET").await;
66}
Modified lib/issues/tests/issues/main.rs lib/issues/tests/issues/main.rs +1 −0 View lib/issues/tests/issues/main.rs
@@ -28,6 +28,7 @@
2828
2929/// A syntactically valid issue UUID used in test URIs.
3030pub(crate) const ISSUE_ID: &str = "11111111-1111-1111-1111-111111111111";
31pub(crate) const ISSUES_ID: &str = "22222222-2222-2222-2222-222222222222";
3132/// A syntactically valid space name used in test URIs.
3233pub(crate) const SPACE_NAME: &str = "test-space";
3334