Skip to content
Clone

feat(api): add admin user management and update issue paths for consistency

ZhenYi committed
parent fb04cec commit c2013a0 Browse files

111 files changed

+750 −426

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

File tree 111
Modified app/apihost/src/main.rs View app/apihost/src/main.rs
@@ -24,6 +24,9 @@
2424 .context("building AppState")?;
2525 info!("dependencies ready");
2626
27 let meter_provider = webapi::metrics::init_meter_provider()
28 .map_err(|err| anyhow::anyhow!("failed to init meter provider: {err}"))?;
29
2730 tokio::spawn(chat::worker::run_background_tasks(state.clone()));
2831
2932 let api_router: Router = router(state)
@@ -49,6 +52,10 @@
4952 .await
5053 .context("axum server error")?;
5154
55 if let Err(err) = meter_provider.shutdown() {
56 warn!(error = %err, "failed to shut down meter provider");
57 }
58
5259 info!("apihost shut down");
5360 Ok(())
5461}
Modified config/default.toml View config/default.toml
@@ -224,6 +224,7 @@
224224enabled = true
225225bind_addr = "0.0.0.0"
226226port = 3000
227admin_uids = []
227228
228229[githost]
229230enabled = true
Modified lib/auth/src/handlers/active_suspension.rs View lib/auth/src/handlers/active_suspension.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/suspension",
12 path = "/me/suspension",
1313 tag = "suspension",
1414 responses(
1515 (status = 200, description = "active suspension for the current user, none when absent", body = AppResponseData<Option<SuspensionDto>>)
Modified lib/auth/src/handlers/cancel_deletion.rs View lib/auth/src/handlers/cancel_deletion.rs
@@ -8,7 +8,7 @@
88
99#[utoipa::path(
1010 post,
11 path = "/deletion/cancel",
11 path = "/me/deletion",
1212 tag = "deletion",
1313 responses(
1414 (status = 200, description = "deletion request cancelled", body = AppResponseData<NoData>)
Modified lib/auth/src/handlers/create_report.rs View lib/auth/src/handlers/create_report.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/reports",
12 path = "/me/reports",
1313 tag = "reports",
1414 request_body = AppRequestData<CreateReportCmd>,
1515 responses(
Modified lib/auth/src/handlers/delete_notification.rs View lib/auth/src/handlers/delete_notification.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/notifications/{notification_id}",
12 path = "/me/notifications/{notification_id}",
1313 tag = "notifications",
1414 params(
1515 ("notification_id" = Uuid, Path, description = "notification id")
Modified lib/auth/src/handlers/deletion_status.rs View lib/auth/src/handlers/deletion_status.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/deletion",
12 path = "/me/deletion",
1313 tag = "deletion",
1414 responses(
1515 (status = 200, description = "latest deletion request for the current user", body = AppResponseData<Option<DeletionDto>>)
Modified lib/auth/src/handlers/get_preferences.rs View lib/auth/src/handlers/get_preferences.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/auth/preferences",
12 path = "/me/preferences",
1313 tag = "account",
1414 responses(
1515 (status = 200, description = "preferences for the current user, defaults when unset", body = AppResponseData<UserPreferencesDto>)
Modified lib/auth/src/handlers/get_user.rs View lib/auth/src/handlers/get_user.rs
@@ -8,10 +8,10 @@
88
99#[utoipa::path(
1010 get,
11 path = "/users/{uid}",
11 path = "/users/{user_id}",
1212 tag = "users",
1313 params(
14 ("uid" = Uuid, Path, description = "user id")
14 ("user_id" = Uuid, Path, description = "user id")
1515 ),
1616 responses(
1717 (status = 200, description = "public user identity", body = AppResponseData<UserPublic>)
@@ -19,8 +19,8 @@
1919)]
2020pub async fn get_user(
2121 State(service): State<AuthService>,
22 Path(uid): Path<Uuid>,
22 Path(user_id): Path<Uuid>,
2323) -> Result<AppResponseData<UserPublic>, ApiError> {
24 let user = service.public_user(uid).await.map_err(ApiError::from)?;
24 let user = service.public_user(user_id).await.map_err(ApiError::from)?;
2525 Ok(AppResponseData::ok(user))
2626}
Modified lib/auth/src/handlers/list_notifications.rs View lib/auth/src/handlers/list_notifications.rs
@@ -17,7 +17,7 @@
1717
1818#[utoipa::path(
1919 get,
20 path = "/notifications",
20 path = "/me/notifications",
2121 tag = "notifications",
2222 params(ListNotificationsQuery),
2323 responses(
Modified lib/auth/src/handlers/mark_all_notifications_read.rs View lib/auth/src/handlers/mark_all_notifications_read.rs
@@ -8,7 +8,7 @@
88
99#[utoipa::path(
1010 put,
11 path = "/notifications/read-all",
11 path = "/me/notifications/read-state",
1212 tag = "notifications",
1313 responses(
1414 (status = 200, description = "all notifications marked as read", body = AppResponseData<NoData>)
Modified lib/auth/src/handlers/mark_notification_read.rs View lib/auth/src/handlers/mark_notification_read.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 put,
12 path = "/notifications/{notification_id}/read",
12 path = "/me/notifications/{notification_id}/read",
1313 tag = "notifications",
1414 params(
1515 ("notification_id" = Uuid, Path, description = "notification id")
Modified lib/auth/src/handlers/me.rs View lib/auth/src/handlers/me.rs
@@ -17,7 +17,7 @@
1717
1818#[utoipa::path(
1919 get,
20 path = "/auth/me",
20 path = "/me",
2121 tag = "account",
2222 responses(
2323 (status = 200, description = "identity and profile of the active user", body = AppResponseData<MeResp>)
Modified lib/auth/src/handlers/mod.rs View lib/auth/src/handlers/mod.rs
@@ -40,7 +40,6 @@
4040pub mod totp_setup;
4141pub mod totp_status;
4242pub mod unread_notifications_count;
43pub mod update_birthday;
4443pub mod update_preferences;
4544pub mod update_profile;
4645pub mod verify_email;
Modified lib/auth/src/handlers/request_deletion.rs View lib/auth/src/handlers/request_deletion.rs
@@ -16,7 +16,7 @@
1616
1717#[utoipa::path(
1818 post,
19 path = "/deletion",
19 path = "/me/deletion",
2020 tag = "deletion",
2121 request_body = AppRequestData<RequestDeletionCmd>,
2222 responses(
Modified lib/auth/src/handlers/unread_notifications_count.rs View lib/auth/src/handlers/unread_notifications_count.rs
@@ -15,7 +15,7 @@
1515
1616#[utoipa::path(
1717 get,
18 path = "/notifications/unread-count",
18 path = "/me/notifications/unread-count",
1919 tag = "notifications",
2020 responses(
2121 (status = 200, description = "number of unread notifications", body = AppResponseData<UnreadCountResp>)
Deleted lib/auth/src/handlers/update_birthday.rs
@@ -1,37 +0,0 @@
1use axum::extract::State;
2use export::rest::{AppRequest, AppRequestData, AppResponseData, NoData};
3use serde::{Deserialize, Serialize};
4use session::Session;
5use time::Date;
6use utoipa::ToSchema;
7
8use crate::errors::ApiError;
9use crate::services::AuthService;
10use session::share::session::require_uid;
11
12#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
13pub struct UpdateBirthdayCmd {
14 pub date_of_birth: Date,
15}
16
17#[utoipa::path(
18 put,
19 path = "/auth/me/birthday",
20 tag = "account",
21 request_body = AppRequestData<UpdateBirthdayCmd>,
22 responses(
23 (status = 200, description = "birthday stored", body = AppResponseData<NoData>)
24 )
25)]
26pub async fn update_birthday(
27 State(service): State<AuthService>,
28 session: Session,
29 payload: AppRequest<UpdateBirthdayCmd>,
30) -> Result<AppResponseData<NoData>, ApiError> {
31 let uid = require_uid(&session)?;
32 service
33 .update_birthday(uid, payload.into_inner().date_of_birth)
34 .await
35 .map_err(ApiError::from)?;
36 Ok(AppResponseData::empty())
37}
Modified lib/auth/src/handlers/update_preferences.rs View lib/auth/src/handlers/update_preferences.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 patch,
12 path = "/auth/preferences",
12 path = "/me/preferences",
1313 tag = "account",
1414 request_body = AppRequestData<UpdatePreferencesCmd>,
1515 responses(
Modified lib/auth/src/handlers/update_profile.rs View lib/auth/src/handlers/update_profile.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 patch,
12 path = "/auth/me",
12 path = "/me",
1313 tag = "account",
1414 request_body = AppRequestData<UpdateProfileCmd>,
1515 responses(
Modified lib/auth/src/services/profile.rs View lib/auth/src/services/profile.rs
@@ -55,6 +55,7 @@
5555 pub website: Option<String>,
5656 pub location: Option<String>,
5757 pub company: Option<String>,
58 pub date_of_birth: Option<Date>,
5859 pub show_email: Option<bool>,
5960 pub show_location: Option<bool>,
6061 pub show_real_name: Option<bool>,
@@ -128,6 +129,9 @@
128129 if let Some(timezone) = cmd.timezone {
129130 user_active.timezone = Set(Some(timezone));
130131 }
132 if let Some(dob) = cmd.date_of_birth {
133 validate_age(Some(dob), self.config().auth.min_age)?;
134 }
131135 user_active.updated_at = Set(now);
132136
133137 TransactionTrait::transaction(db.primary(), |txn| {
@@ -155,6 +159,9 @@
155159 if let Some(value) = cmd.company {
156160 active.company = Set(Some(value));
157161 }
162 if let Some(value) = cmd.date_of_birth {
163 active.date_of_birth = Set(Some(value));
164 }
158165 if let Some(value) = cmd.show_email {
159166 active.show_email = Set(value);
160167 }
@@ -179,7 +186,7 @@
179186 location: Set(cmd.location),
180187 company: Set(cmd.company),
181188 pronouns_privacy: Set(None),
182 date_of_birth: Set(None),
189 date_of_birth: Set(cmd.date_of_birth),
183190 banner_url: Set(None),
184191 banner_hash: Set(None),
185192 banner_color: Set(None),
@@ -203,50 +210,5 @@
203210 })
204211 .await
205212 .map_err(AuthError::from)
206 }
207
208 pub async fn update_birthday(&self, uid: Uuid, date_of_birth: Date) -> Result<bool, AuthError> {
209 validate_age(Some(date_of_birth), self.config().auth.min_age)?;
210 let db = self.db();
211 let now = OffsetDateTime::now_utc();
212 let existing = user_profiles::Entity::find_by_id(uid)
213 .one(db.write())
214 .await?;
215 if let Some(row) = existing {
216 let mut active = row.into_active_model();
217 active.date_of_birth = Set(Some(date_of_birth));
218 active.updated_at = Set(now);
219 active.update(db.write()).await?;
220 Ok(true)
221 } else {
222 let row = user_profiles::ActiveModel {
223 user_id: Set(uid),
224 real_name: Set(None),
225 bio: Set(None),
226 pronouns: Set(None),
227 website: Set(None),
228 location: Set(None),
229 company: Set(None),
230 pronouns_privacy: Set(None),
231 date_of_birth: Set(Some(date_of_birth)),
232 banner_url: Set(None),
233 banner_hash: Set(None),
234 banner_color: Set(None),
235 accent_color: Set(None),
236 timezone_privacy_flags: Set(None),
237 custom_status_emoji: Set(None),
238 custom_status_text: Set(None),
239 custom_status_expires_at: Set(None),
240 show_email: Set(false),
241 show_location: Set(false),
242 show_real_name: Set(false),
243 show_activity: Set(false),
244 privacy_agreed_at: Set(None),
245 created_at: Set(now),
246 updated_at: Set(now),
247 };
248 row.insert(db.write()).await?;
249 Ok(false)
250 }
251213 }
252214}
Modified lib/auth/src/share/guard.rs View lib/auth/src/share/guard.rs
@@ -1,7 +1,9 @@
11use axum::extract::{Request, State};
2use axum::http::StatusCode;
23use axum::middleware::Next;
3use axum::response::Response;
4use axum::response::{IntoResponse, Response};
45use session::Session;
6use session::SessionUser;
57use session::share::guard::session_guard;
68
79use crate::services::AuthService;
@@ -14,3 +16,19 @@
1416) -> Response {
1517 session_guard::<AuthService>(State(service), session, request, next).await
1618}
19
20pub async fn admin_guard(
21 State(service): State<AuthService>,
22 session: Session,
23 request: Request,
24 next: Next,
25) -> Response {
26 match session.uid() {
27 Some(uid) if service.config().apihost.is_platform_admin(&uid.to_string()) => {}
28 Some(_) => {
29 return (StatusCode::FORBIDDEN, "platform admin required").into_response();
30 }
31 None => {}
32 }
33 session_guard::<AuthService>(State(service), session, request, next).await
34}
Modified lib/auth/src/share/types.rs View lib/auth/src/share/types.rs
@@ -1,5 +1,4 @@
11use serde::{Deserialize, Serialize};
2use utoipa::IntoParams;
32use uuid::Uuid;
43
54#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
@@ -22,19 +21,7 @@
2221 pub avatar_url: String,
2322}
2423
25#[derive(Debug, Clone, Deserialize, IntoParams)]
26pub struct ListQuery {
27 pub limit: Option<u64>,
28 pub offset: Option<u64>,
29}
30
31impl ListQuery {
32 pub fn page(&self) -> (u64, u64) {
33 let limit = self.limit.unwrap_or(50).clamp(1, 100);
34 let offset = self.offset.unwrap_or(0);
35 (limit, offset)
36 }
37}
24pub type ListQuery = export::pagination::PageQuery;
3825
3926#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
4027pub struct AgreementDoc {
Modified lib/auth/src/errors.rs View lib/auth/src/errors.rs
@@ -150,16 +150,16 @@
150150impl From<AuthError> for ApiError {
151151 fn from(error: AuthError) -> Self {
152152 match error {
153 AuthError::BadRequest(msg) => Self::BadRequest(AppResponseData::err(400, msg)),
154 AuthError::PowFailed => Self::BadRequest(Self::payload(400, "proof of work failed")),
153 AuthError::BadRequest(msg) => Self::BadRequest(AppResponseData::err(400, msg).with_error_code("bad_request")),
154 AuthError::PowFailed => Self::BadRequest(Self::payload(400, "proof of work failed").with_error_code("pow_failed")),
155155 AuthError::InvalidCredentials => {
156156 Self::InvalidCredentials(Self::payload(400, "invalid credentials"))
157157 }
158 AuthError::Unauthorized => Self::Unauthorized(Self::payload(401, "unauthorized")),
159 AuthError::Forbidden => Self::Forbidden(Self::payload(403, "forbidden")),
160 AuthError::NotFound => Self::NotFound(Self::payload(404, "not found")),
161 AuthError::Conflict(msg) => Self::Conflict(AppResponseData::err(409, msg)),
162 AuthError::RateLimited => Self::RateLimited(Self::payload(429, "rate limit exceeded")),
158 AuthError::Unauthorized => Self::Unauthorized(Self::payload(401, "unauthorized").with_error_code("unauthorized")),
159 AuthError::Forbidden => Self::Forbidden(Self::payload(403, "forbidden").with_error_code("forbidden")),
160 AuthError::NotFound => Self::NotFound(Self::payload(404, "not found").with_error_code("not_found")),
161 AuthError::Conflict(msg) => Self::Conflict(AppResponseData::err(409, msg).with_error_code("conflict")),
162 AuthError::RateLimited => Self::RateLimited(Self::payload(429, "rate limit exceeded").with_error_code("rate_limited")),
163163 AuthError::Internal(message) => {
164164 tracing::warn!(error = %message, "auth handler failed");
165165 Self::Internal(Self::payload(500, "internal server error"))
Modified lib/auth/src/openapi.rs View lib/auth/src/openapi.rs
@@ -14,7 +14,6 @@
1414use crate::handlers::pow_challenge::PowChallengeResp;
1515use crate::handlers::request_deletion::RequestDeletionCmd;
1616use crate::handlers::unread_notifications_count::UnreadCountResp;
17use crate::handlers::update_birthday::UpdateBirthdayCmd;
1817use crate::services::access_keys::{AccessKeyDto, CreateAccessKeyCmd, CreateAccessKeyResp};
1918use crate::services::agreements::AcceptAgreementResp;
2019use crate::services::deletion::DeletionDto;
@@ -84,7 +83,6 @@
8483 handlers::totp_setup::totp_setup,
8584 handlers::totp_status::totp_status,
8685 handlers::unread_notifications_count::unread_notifications_count,
87 handlers::update_birthday::update_birthday,
8886 handlers::update_preferences::update_preferences,
8987 handlers::update_profile::update_profile,
9088 handlers::verify_email::verify_email,
@@ -127,7 +125,6 @@
127125 UserMeDto,
128126 ProfileDto,
129127 UpdateProfileCmd,
130 UpdateBirthdayCmd,
131128 UserPreferencesDto,
132129 UpdatePreferencesCmd,
133130 AcceptAgreementCmd,
Modified lib/auth/src/routes.rs View lib/auth/src/routes.rs
@@ -44,7 +44,6 @@
4444use crate::handlers::totp_setup::totp_setup;
4545use crate::handlers::totp_status::totp_status;
4646use crate::handlers::unread_notifications_count::unread_notifications_count;
47use crate::handlers::update_birthday::update_birthday;
4847use crate::handlers::update_preferences::update_preferences;
4948use crate::handlers::update_profile::update_profile;
5049use crate::handlers::verify_email::verify_email;
@@ -60,15 +59,12 @@
6059 .route("/auth/login/2fa", post(login_2fa))
6160 .route("/auth/logout", post(logout))
6261 .route("/auth/email/verify", post(verify_email))
63 .route("/auth/email/verify/page", get(verify_email_page))
6462 .route("/auth/email/resend", post(resend_code))
6563 .route("/auth/email/reclaim", post(reclaim))
6664 .route("/auth/password/forgot", post(forgot_password))
6765 .route("/auth/password/reset", post(reset_password))
68 .route("/auth/password/reset/page", get(reset_password_page))
6966 .route("/auth/agreements", get(list_agreements))
70 .route("/auth/agreements/{key}/page", get(agreement_page))
71 .route("/users/{uid}", get(get_user));
67 .route("/users/{user_id}", get(get_user));
7268
7369 let authed = Router::new()
7470 .route("/auth/email/change", post(request_email_change))
@@ -93,32 +89,46 @@
9389 .route("/auth/access-keys/{key_id}", delete(delete_access_key))
9490 .route("/auth/ssh-keys", get(list_ssh_keys).post(add_ssh_key))
9591 .route("/auth/ssh-keys/{key_id}", delete(delete_ssh_key))
96 .route("/auth/me", get(me).patch(update_profile))
97 .route("/auth/me/birthday", put(update_birthday))
92 .route("/me", get(me).patch(update_profile))
9893 .route(
99 "/auth/preferences",
94 "/me/preferences",
10095 get(get_preferences).patch(update_preferences),
10196 )
102 .route("/auth/agreements/{key}", put(accept_agreement))
103 .route("/notifications", get(list_notifications))
97 .route("/me/agreements/{key}", put(accept_agreement))
98 .route("/me/notifications", get(list_notifications))
10499 .route(
105 "/notifications/unread-count",
100 "/me/notifications/unread-count",
106101 get(unread_notifications_count),
107102 )
108 .route("/notifications/read-all", put(mark_all_notifications_read))
109103 .route(
110 "/notifications/{notification_id}",
104 "/me/notifications/read-state",
105 put(mark_all_notifications_read),
106 )
107 .route(
108 "/me/notifications/{notification_id}",
111109 delete(delete_notification),
112110 )
113111 .route(
114 "/notifications/{notification_id}/read",
112 "/me/notifications/{notification_id}/read",
115113 put(mark_notification_read),
116114 )
117 .route("/reports", post(create_report))
118 .route("/deletion", get(deletion_status).post(request_deletion))
119 .route("/deletion/cancel", post(cancel_deletion))
120 .route("/suspension", get(active_suspension))
115 .route("/me/reports", post(create_report))
116 .route(
117 "/me/deletion",
118 get(deletion_status)
119 .post(request_deletion)
120 .delete(cancel_deletion),
121 )
122 .route("/me/suspension", get(active_suspension))
121123 .route_layer(middleware::from_fn_with_state(service, auth_guard));
122124
123125 public.merge(authed)
124126}
127
128pub fn pages_router(service: AuthService) -> Router {
129 Router::new()
130 .route("/email/verify", get(verify_email_page))
131 .route("/password/reset", get(reset_password_page))
132 .route("/agreements/{key}", get(agreement_page))
133 .with_state(service)
134}
Modified lib/chat/src/errors.rs View lib/chat/src/errors.rs
@@ -161,12 +161,12 @@
161161impl From<ChatError> for ApiError {
162162 fn from(error: ChatError) -> Self {
163163 match error {
164 ChatError::BadRequest(msg) => Self::BadRequest(AppResponseData::err(400, msg)),
165 ChatError::Unauthorized => Self::Unauthorized(Self::payload(401, "unauthorized")),
166 ChatError::Forbidden => Self::Forbidden(Self::payload(403, "forbidden")),
167 ChatError::NotFound => Self::NotFound(Self::payload(404, "not found")),
168 ChatError::Conflict(msg) => Self::Conflict(AppResponseData::err(409, msg)),
169 ChatError::RateLimited(msg) => Self::TooManyRequests(AppResponseData::err(429, msg)),
164 ChatError::BadRequest(msg) => Self::BadRequest(AppResponseData::err(400, msg).with_error_code("bad_request")),
165 ChatError::Unauthorized => Self::Unauthorized(Self::payload(401, "unauthorized").with_error_code("unauthorized")),
166 ChatError::Forbidden => Self::Forbidden(Self::payload(403, "forbidden").with_error_code("forbidden")),
167 ChatError::NotFound => Self::NotFound(Self::payload(404, "not found").with_error_code("not_found")),
168 ChatError::Conflict(msg) => Self::Conflict(AppResponseData::err(409, msg).with_error_code("conflict")),
169 ChatError::RateLimited(msg) => Self::TooManyRequests(AppResponseData::err(429, msg).with_error_code("rate_limited")),
170170 ChatError::Internal(message) => {
171171 tracing::warn!(error = %message, "chat handler failed");
172172 Self::Internal(Self::payload(500, "internal server error"))
Modified lib/config/src/apihost.rs View lib/config/src/apihost.rs
@@ -8,9 +8,14 @@
88 pub enabled: Option<bool>,
99 pub bind_addr: Option<String>,
1010 pub port: Option<u16>,
11 pub admin_uids: Vec<String>,
1112}
1213
1314impl ApihostConfig {
15 pub fn is_platform_admin(&self, uid: &str) -> bool {
16 self.admin_uids.iter().any(|id| id == uid)
17 }
18
1419 pub fn enabled(&self) -> bool {
1520 self.enabled.unwrap_or(true)
1621 }
Modified lib/export/src/lib.rs View lib/export/src/lib.rs
@@ -1,5 +1,7 @@
11pub mod database;
22pub mod error;
3pub mod middleware;
4pub mod pagination;
35pub mod redis;
46pub mod rest;
57pub mod storage;
Added lib/export/src/middleware.rs View lib/export/src/middleware.rs
@@ -0,0 +1,131 @@
1use std::future::Future;
2use std::pin::Pin;
3use std::task::{Context, Poll};
4
5use axum::body::Body;
6use axum::http::{HeaderName, HeaderValue, Request, Response, header};
7use bytes::Bytes;
8use http_body_util::BodyExt;
9use serde_json::Value;
10use tower::{Layer, Service};
11use uuid::Uuid;
12
13pub const REQUEST_ID_HEADER: HeaderName = HeaderName::from_static("x-request-id");
14pub const LOG_ID_HEADER: HeaderName = HeaderName::from_static("x-log-id");
15
16const MAX_INBOUND_ID_LEN: usize = 128;
17
18#[derive(Debug, Clone)]
19pub struct RequestId(pub String);
20
21#[derive(Debug, Clone)]
22pub struct LogId(pub String);
23
24#[derive(Debug, Clone, Copy, Default)]
25pub struct AppTraceLayer;
26
27impl AppTraceLayer {
28 pub fn new() -> Self {
29 Self
30 }
31}
32
33impl<S> Layer<S> for AppTraceLayer {
34 type Service = AppTraceService<S>;
35
36 fn layer(&self, inner: S) -> Self::Service {
37 AppTraceService { inner }
38 }
39}
40
41#[derive(Debug, Clone)]
42pub struct AppTraceService<S> {
43 inner: S,
44}
45
46impl<S> Service<Request<Body>> for AppTraceService<S>
47where
48 S: Service<Request<Body>, Response = Response<Body>> + Send + 'static,
49 S::Future: Send + 'static,
50{
51 type Response = S::Response;
52 type Error = S::Error;
53 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
54
55 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
56 self.inner.poll_ready(cx)
57 }
58
59 fn call(&mut self, mut req: Request<Body>) -> Self::Future {
60 let request_id =
61 inbound_id(req.headers().get(&REQUEST_ID_HEADER)).unwrap_or_else(new_id);
62 let log_id = new_id();
63
64 req.extensions_mut().insert(RequestId(request_id.clone()));
65 req.extensions_mut().insert(LogId(log_id.clone()));
66
67 let fut = self.inner.call(req);
68 Box::pin(async move {
69 let mut res = fut.await?;
70 if let Ok(value) = HeaderValue::try_from(request_id.as_str()) {
71 res.headers_mut().insert(REQUEST_ID_HEADER, value);
72 }
73 if let Ok(value) = HeaderValue::try_from(log_id.as_str()) {
74 res.headers_mut().insert(LOG_ID_HEADER, value);
75 }
76 inject_ids(&mut res, &request_id, &log_id).await;
77 Ok(res)
78 })
79 }
80}
81
82fn new_id() -> String {
83 Uuid::now_v7().to_string()
84}
85
86fn inbound_id(value: Option<&HeaderValue>) -> Option<String> {
87 let value = value?.to_str().ok()?.trim();
88 if value.is_empty() || value.len() > MAX_INBOUND_ID_LEN {
89 return None;
90 }
91 Some(value.to_string())
92}
93
94async fn inject_ids(res: &mut Response<Body>, request_id: &str, log_id: &str) {
95 let is_json = res
96 .headers()
97 .get(header::CONTENT_TYPE)
98 .and_then(|value| value.to_str().ok())
99 .is_some_and(|value| value.starts_with("application/json"));
100 if !is_json {
101 return;
102 }
103
104 let (mut parts, body) = std::mem::take(res).into_parts();
105 let bytes = match body.collect().await {
106 Ok(collected) => collected.to_bytes(),
107 Err(_) => {
108 *res = Response::from_parts(parts, Body::empty());
109 return;
110 }
111 };
112
113 let output = match serde_json::from_slice::<Value>(&bytes) {
114 Ok(Value::Object(mut map))
115 if map.contains_key("request_id") && map.contains_key("log_id") =>
116 {
117 map.insert("request_id".to_string(), Value::String(request_id.to_string()));
118 map.insert("log_id".to_string(), Value::String(log_id.to_string()));
119 match serde_json::to_vec(&map) {
120 Ok(rewritten) => Bytes::from(rewritten),
121 Err(_) => bytes,
122 }
123 }
124 _ => bytes,
125 };
126
127 if let Ok(length) = HeaderValue::try_from(output.len().to_string()) {
128 parts.headers.insert(header::CONTENT_LENGTH, length);
129 }
130 *res = Response::from_parts(parts, Body::from(output));
131}
Added lib/export/src/pagination.rs View lib/export/src/pagination.rs
@@ -0,0 +1,33 @@
1use serde::{Deserialize, Serialize};
2use utoipa::{IntoParams, ToSchema};
3
4pub const DEFAULT_PAGE_LIMIT: u64 = 50;
5pub const MAX_PAGE_LIMIT: u64 = 100;
6
7#[derive(Debug, Clone, Copy, Deserialize, IntoParams, ToSchema)]
8pub struct PageQuery {
9 pub limit: Option<u64>,
10 pub offset: Option<u64>,
11}
12
13impl PageQuery {
14 pub fn limit(&self) -> u64 {
15 self.limit.unwrap_or(DEFAULT_PAGE_LIMIT).clamp(1, MAX_PAGE_LIMIT)
16 }
17
18 pub fn offset(&self) -> u64 {
19 self.offset.unwrap_or(0)
20 }
21
22 pub fn page(&self) -> (u64, u64) {
23 (self.limit(), self.offset())
24 }
25}
26
27#[derive(Debug, Clone, Serialize, ToSchema)]
28pub struct Page<T> {
29 pub items: Vec<T>,
30 pub total: i64,
31 pub limit: u64,
32 pub offset: u64,
33}
Modified lib/export/src/rest.rs View lib/export/src/rest.rs
@@ -17,9 +17,8 @@
1717 pub message: String,
1818 pub data: Option<T>,
1919 pub error: Option<String>,
20 pub duration: String,
20 pub error_code: Option<String>,
2121 pub request_id: String,
22 pub at: String,
2322 pub log_id: String,
2423}
2524
@@ -30,6 +29,7 @@
3029 pub data: Option<T>,
3130}
3231
32
3333impl<T> AppResponseData<T>
3434where
3535 T: Serialize,
@@ -40,9 +40,8 @@
4040 message: "success".to_string(),
4141 data: Some(data),
4242 error: None,
43 duration: String::new(),
43 error_code: None,
4444 request_id: String::new(),
45 at: String::new(),
4645 log_id: String::new(),
4746 }
4847 }
@@ -53,9 +52,8 @@
5352 message: message.into(),
5453 data: None,
5554 error: None,
56 duration: String::new(),
55 error_code: None,
5756 request_id: String::new(),
58 at: String::new(),
5957 log_id: String::new(),
6058 }
6159 }
@@ -70,9 +68,8 @@
7068 message: message.into(),
7169 data: None,
7270 error: Some(detail.into()),
73 duration: String::new(),
71 error_code: None,
7472 request_id: String::new(),
75 at: String::new(),
7673 log_id: String::new(),
7774 }
7875 }
@@ -92,8 +89,8 @@
9289 self
9390 }
9491
95 pub fn with_duration(mut self, duration: impl Into<String>) -> Self {
96 self.duration = duration.into();
92 pub fn with_error_code(mut self, code: impl Into<String>) -> Self {
93 self.error_code = Some(code.into());
9794 self
9895 }
9996
@@ -102,11 +99,6 @@
10299 self
103100 }
104101
105 pub fn with_at(mut self, at: impl Into<String>) -> Self {
106 self.at = at.into();
107 self
108 }
109
110102 pub fn with_log_id(mut self, log_id: impl Into<String>) -> Self {
111103 self.log_id = log_id.into();
112104 self
@@ -120,23 +112,8 @@
120112 message: "success".to_string(),
121113 data: None,
122114 error: None,
123 duration: String::new(),
124 request_id: String::new(),
125 at: String::new(),
126 log_id: String::new(),
127 }
128 }
129
130 pub fn err_plain(code: i32, message: impl Into<String>) -> Self {
131 let msg = message.into();
132 AppResponseData {
133 code,
134 message: msg.clone(),
135 data: None,
136 error: Some(msg),
137 duration: String::new(),
115 error_code: None,
138116 request_id: String::new(),
139 at: String::new(),
140117 log_id: String::new(),
141118 }
142119 }
Modified lib/export/Cargo.toml View lib/export/Cargo.toml
@@ -13,4 +13,10 @@
1313axum = { workspace = true }
1414serde = { workspace = true, features = ["derive"] }
1515tracing = { workspace = true }
16utoipa = { workspace = true }
No newline at end of file
16utoipa = { workspace = true }
17rand.workspace = true
18tower = { workspace = true }
19serde_json = { workspace = true }
20uuid = { workspace = true }
21bytes = { workspace = true }
22http-body-util = { workspace = true }
No newline at end of file
Modified lib/git/src/handler/branches/set_default.rs View lib/git/src/handler/branches/set_default.rs
@@ -14,7 +14,7 @@
1414}
1515
1616#[utoipa::path(
17 post,
17 put,
1818 path = "/repos/{space_name}/{repo_name}/branches/{name}/default",
1919 tag = "branches",
2020 params(BranchPath),
Modified lib/git/src/handler/hooks/execute_script.rs View lib/git/src/handler/hooks/execute_script.rs
@@ -6,13 +6,13 @@
66use crate::errors::{ApiError, GitApiError};
77use crate::handler::require_writer;
88use crate::service::GitService;
9use crate::service::hooks::ScriptOutputDto;
9use crate::service::hooks::{HookEventKind, ScriptOutputDto};
1010
1111#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1212pub struct HookScriptPath {
1313 pub space_name: String,
1414 pub repo_name: String,
15 pub script: String,
15 pub script: HookEventKind,
1616}
1717
1818#[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)]
Modified lib/git/src/service/hooks.rs View lib/git/src/service/hooks.rs
@@ -4,7 +4,7 @@
44use crate::service::gitcmd::run_exec_cmd;
55use crate::share::errors::GitError;
66
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
88#[serde(rename_all = "kebab-case")]
99pub enum HookEventKind {
1010 PreReceive,
@@ -67,22 +67,27 @@
6767 &self,
6868 space_name: String,
6969 repo_name: String,
70 script: String,
70 script: HookEventKind,
7171 args: Vec<String>,
7272 ) -> Result<ScriptOutputDto, GitError> {
73 if script.is_empty()
74 || script.contains('/')
75 || script.contains('\\')
76 || script.contains("..")
77 || script.starts_with('-')
78 {
73 if !self.hook_supported(script) {
7974 return Err(GitError::InvalidArgument(format!(
80 "invalid script name `{script}`"
75 "unsupported hook script `{script:?}`"
8176 )));
8277 }
78 if args.iter().any(|a| a.len() > 1024) || args.len() > 16 {
79 return Err(GitError::InvalidArgument(
80 "invalid hook arguments".to_string(),
81 ));
82 }
83 let filename = match script {
84 HookEventKind::PreReceive => "pre-receive",
85 HookEventKind::PostReceive => "post-receive",
86 _ => unreachable!("rejected by hook_supported"),
87 };
8388 let ctx = self.context(space_name, repo_name).await?;
8489 let dir = ctx.repo_dir().join("hooks");
85 let prog = dir.join(&script);
90 let prog = dir.join(filename);
8691 let timeout = self.state.config.git.git_command_timeout_secs;
8792 let out = run_exec_cmd(
8893 "hooks::execute_script",
Modified lib/git/src/openapi.rs View lib/git/src/openapi.rs
@@ -33,16 +33,12 @@
3333 handler::commits::rev_parse::rev_parse,
3434 handler::commits::set_commit_status::set_commit_status,
3535 handler::commits::stats::commit_stats,
36 handler::commits::sync_commit::sync_commit,
3736 handler::commits::tree_entries::get_tree_entries,
3837 handler::conflicts::check::conflict_check,
3938 handler::conflicts::check_threeway::conflict_check_threeway,
4039 handler::conflicts::conflicted_files::conflicted_files,
4140 handler::conflicts::detail::conflict_detail,
4241 handler::content::caller_signature::caller_signature,
43 handler::content::commit_object::commit_object,
44 handler::content::write_blob::write_blob,
45 handler::content::write_tree_from_files::write_tree_from_files,
4642 handler::diffs::files_changed_between::files_changed_between,
4743 handler::diffs::for_commit::diff_for_commit,
4844 handler::diffs::range_diff::range_diff,
@@ -54,7 +50,6 @@
5450 handler::licenses::get_license,
5551 handler::licenses::list_licenses,
5652 handler::licenses::upsert_license,
57 handler::lfs::cleanup_unreferenced::cleanup_unreferenced,
5853 handler::lfs::list_locks::list_locks,
5954 handler::lfs::lock::lock,
6055 handler::lfs::unlock::unlock,
@@ -64,27 +59,11 @@
6459 handler::logs::graph::log_graph,
6560 handler::logs::last_commit::log_last_commit,
6661 handler::logs::list::log_list,
67 handler::maintenance::fsck::fsck,
68 handler::maintenance::prune::prune,
69 handler::packs::ingest::ingest,
70 handler::packs::repack::repack,
71 handler::packs::stats::stats,
72 handler::patches::apply::patch_apply,
7362 handler::patches::generate::patch_generate,
7463 handler::patches::parse::patch_parse,
7564 handler::path_log::path_log,
76 handler::pipelines::cancel::cancel,
7765 handler::pipelines::schedule::schedule,
7866 handler::pipelines::trigger::trigger,
79 handler::refs::branches::refs_branches,
80 handler::refs::delete::refs_delete,
81 handler::refs::filtered::refs_list_filtered,
82 handler::refs::get::refs_get,
83 handler::refs::head::refs_head,
84 handler::refs::is_object_in_ref::refs_is_object_in_ref,
85 handler::refs::list::refs_list,
86 handler::refs::resolve::refs_resolve,
87 handler::refs::update::refs_update,
8867 handler::remotes::add::add,
8968 handler::remotes::clone_import::clone_import,
9069 handler::remotes::fetch::fetch,
@@ -152,6 +131,7 @@
152131 ),
153132 components(schemas(
154133 export::rest::NoData,
134 crate::service::hooks::HookEventKind,
155135 models::common::watch_mode::WatchMode,
156136 models::git::git_commit_status::CommitStatusState,
157137 models::git::git_tags::TagType,
Modified lib/git/src/routes.rs View lib/git/src/routes.rs
@@ -123,6 +123,13 @@
123123 .layer(Extension(schema))
124124}
125125
126pub fn internal_router(service: GitService) -> Router<GitService> {
127 let schema = crate::graphql::build_schema(service.state.clone());
128 Router::new()
129 .nest("/repos", repos_internal_router())
130 .layer(Extension(schema))
131}
132
126133fn repos_router() -> Router<GitService> {
127134 Router::new()
128135 .route("/{space_name}", post(repo_create).get(repo_list))
@@ -141,7 +148,7 @@
141148 )
142149 .route(
143150 "/{space_name}/{repo_name}/branches/{name}/default",
144 post(branch_set_default),
151 put(branch_set_default),
145152 )
146153 .route(
147154 "/{space_name}/{repo_name}/tags",
@@ -155,25 +162,6 @@
155162 "/{space_name}/{repo_name}/tags/{name}/verify",
156163 get(tag_verify),
157164 )
158 .route("/{space_name}/{repo_name}/refs", get(refs_list))
159 .route("/{space_name}/{repo_name}/refs/head", get(refs_head))
160 .route(
161 "/{space_name}/{repo_name}/refs/branches",
162 get(refs_branches),
163 )
164 .route("/{space_name}/{repo_name}/refs/filtered", get(filtered))
165 .route(
166 "/{space_name}/{repo_name}/refs/contains",
167 get(is_object_in_ref),
168 )
169 .route(
170 "/{space_name}/{repo_name}/refs/{name}",
171 get(refs_get).patch(refs_update).delete(refs_delete),
172 )
173 .route(
174 "/{space_name}/{repo_name}/refs/{name}/resolve",
175 get(refs_resolve),
176 )
177165 .route("/{space_name}/{repo_name}/commits", get(list_commits))
178166 .route(
179167 "/{space_name}/{repo_name}/commits/{revision}",
@@ -188,10 +176,6 @@
188176 "/{space_name}/{repo_name}/commits/ancestor",
189177 get(is_ancestor),
190178 )
191 .route(
192 "/{space_name}/{repo_name}/commits/{revision}/sync",
193 post(sync_commit),
194 )
195179 .route("/{space_name}/{repo_name}/rev/{revision}", get(rev_parse))
196180 .route(
197181 "/{space_name}/{repo_name}/commit-statuses",
@@ -267,7 +251,6 @@
267251 "/{space_name}/{repo_name}/patch/generate",
268252 get(patch_generate),
269253 )
270 .route("/{space_name}/{repo_name}/patch/apply", post(patch_apply))
271254 .route("/_/patch/parse", post(patch_parse))
272255 .route(
273256 "/{space_name}/{repo_name}/conflicts/check",
@@ -311,10 +294,6 @@
311294 delete(unlock_by_id),
312295 )
313296 .route(
314 "/{space_name}/{repo_name}/lfs/cleanup",
315 post(cleanup_unreferenced),
316 )
317 .route(
318297 "/{space_name}/{repo_name}/hooks/{script}",
319298 post(execute_script),
320299 )
@@ -323,22 +302,7 @@
323302 "/{space_name}/{repo_name}/pipelines/schedule",
324303 post(schedule),
325304 )
326 .route("/_/pipelines/cancel", post(cancel))
327 .route("/{space_name}/{repo_name}/packs/stats", get(pack_stats))
328 .route("/{space_name}/{repo_name}/packs/ingest", post(pack_ingest))
329 .route("/{space_name}/{repo_name}/packs/repack", post(pack_repack))
330 .route("/{space_name}/{repo_name}/maintenance/prune", post(prune))
331 .route("/{space_name}/{repo_name}/maintenance/fsck", post(fsck))
332305 .route("/_/contents/signature", post(caller_signature))
333 .route("/{space_name}/{repo_name}/contents/blobs", post(write_blob))
334 .route(
335 "/{space_name}/{repo_name}/contents/trees",
336 post(write_tree_from_files),
337 )
338 .route(
339 "/{space_name}/{repo_name}/contents/commits",
340 post(commit_object),
341 )
342306 .route(
343307 "/{space_name}/{repo_name}/analysis/languages",
344308 get(languages_read).post(languages),
@@ -400,6 +364,65 @@
400364 )
401365}
402366
367fn repos_internal_router() -> Router<GitService> {
368 Router::new()
369 .route("/{space_name}/{repo_name}/refs", get(refs_list))
370 .route("/{space_name}/{repo_name}/refs/head", get(refs_head))
371 .route(
372 "/{space_name}/{repo_name}/refs/branches",
373 get(refs_branches),
374 )
375 .route("/{space_name}/{repo_name}/refs/filtered", get(filtered))
376 .route(
377 "/{space_name}/{repo_name}/refs/contains",
378 get(is_object_in_ref),
379 )
380 .route(
381 "/{space_name}/{repo_name}/refs/{name}",
382 get(refs_get).patch(refs_update).delete(refs_delete),
383 )
384 .route(
385 "/{space_name}/{repo_name}/refs/{name}/resolve",
386 get(refs_resolve),
387 )
388 .route(
389 "/{space_name}/{repo_name}/commits/{revision}/sync",
390 post(sync_commit),
391 )
392 .route(
393 "/{space_name}/{repo_name}/patch/apply",
394 post(patch_apply),
395 )
396 .route("/_/pipelines/cancel", post(cancel))
397 .route("/{space_name}/{repo_name}/packs/stats", get(pack_stats))
398 .route("/{space_name}/{repo_name}/packs/ingest", post(pack_ingest))
399 .route("/{space_name}/{repo_name}/packs/repack", post(pack_repack))
400 .route(
401 "/{space_name}/{repo_name}/maintenance/prune",
402 post(prune),
403 )
404 .route(
405 "/{space_name}/{repo_name}/maintenance/fsck",
406 post(fsck),
407 )
408 .route(
409 "/{space_name}/{repo_name}/lfs/cleanup",
410 post(cleanup_unreferenced),
411 )
412 .route(
413 "/{space_name}/{repo_name}/contents/blobs",
414 post(write_blob),
415 )
416 .route(
417 "/{space_name}/{repo_name}/contents/trees",
418 post(write_tree_from_files),
419 )
420 .route(
421 "/{space_name}/{repo_name}/contents/commits",
422 post(commit_object),
423 )
424}
425
403426fn topics_router() -> Router<GitService> {
404427 Router::new()
405428 .route("/", post(create_topic).get(list_topics))
Modified lib/issues/src/handlers/boards/create_board.rs View lib/issues/src/handlers/boards/create_board.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 post,
13 path = "/issues/{space_name}",
13 path = "/issues/{space_name}/boards",
1414 tag = "issues",
1515 params(SpaceNamePath),
1616 request_body = CreateBoardCmd,
Modified lib/issues/src/handlers/boards/delete_board.rs View lib/issues/src/handlers/boards/delete_board.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}",
12 path = "/issues/{space_name}/boards/{board_id}",
1313 tag = "issues",
1414 params(BoardPath),
1515 responses(
Modified lib/issues/src/handlers/boards/get_board.rs View lib/issues/src/handlers/boards/get_board.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{board_id}",
12 path = "/issues/{space_name}/boards/{board_id}",
1313 tag = "issues",
1414 params(BoardPath),
1515 responses(
Modified lib/issues/src/handlers/boards/list_boards.rs View lib/issues/src/handlers/boards/list_boards.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}",
13 path = "/issues/{space_name}/boards",
1414 tag = "issues",
1515 params(SpaceNamePath, ListBoardQuery),
1616 responses(
Modified lib/issues/src/handlers/boards/update_board.rs View lib/issues/src/handlers/boards/update_board.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 patch,
12 path = "/issues/{space_name}/{board_id}",
12 path = "/issues/{space_name}/boards/{board_id}",
1313 tag = "issues",
1414 params(BoardPath),
1515 request_body = UpdateBoardCmd,
Modified lib/issues/src/handlers/comments/create_comment.rs View lib/issues/src/handlers/comments/create_comment.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 post,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/comments",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/comments",
1414 tag = "issues",
1515 params(IssuePath),
1616 request_body = CreateCommentCmd,
Modified lib/issues/src/handlers/comments/delete_comment.rs View lib/issues/src/handlers/comments/delete_comment.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/comments/{comment_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/comments/{comment_id}",
1313 tag = "issues",
1414 params(CommentPath),
1515 responses(
Modified lib/issues/src/handlers/comments/get_comment.rs View lib/issues/src/handlers/comments/get_comment.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/comments/{comment_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/comments/{comment_id}",
1313 tag = "issues",
1414 params(CommentPath),
1515 responses(
Modified lib/issues/src/handlers/comments/list_comments.rs View lib/issues/src/handlers/comments/list_comments.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/comments",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/comments",
1414 tag = "issues",
1515 params(IssuePath, ListCommentQuery),
1616 responses(
Modified lib/issues/src/handlers/comments/update_comment.rs View lib/issues/src/handlers/comments/update_comment.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 patch,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/comments/{comment_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/comments/{comment_id}",
1313 tag = "issues",
1414 params(CommentPath),
1515 request_body = UpdateCommentCmd,
Modified lib/issues/src/handlers/content_history/list_content_history.rs View lib/issues/src/handlers/content_history/list_content_history.rs
@@ -11,7 +11,7 @@
1111
1212#[utoipa::path(
1313 get,
14 path = "/issues/{space_name}/{board_id}/{issue_id}/history",
14 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/history",
1515 tag = "issues",
1616 params(ContentHistoryPath, ListContentHistoryQuery),
1717 responses(
Modified lib/issues/src/handlers/dependencies/create_dependency.rs View lib/issues/src/handlers/dependencies/create_dependency.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 post,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/dependencies",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/dependencies",
1414 tag = "issues",
1515 params(IssuePath),
1616 request_body = CreateDependencyCmd,
Modified lib/issues/src/handlers/dependencies/delete_dependency.rs View lib/issues/src/handlers/dependencies/delete_dependency.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/dependencies/{dep_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/dependencies/{dep_id}",
1313 tag = "issues",
1414 params(DependencyPath),
1515 responses(
Modified lib/issues/src/handlers/dependencies/list_dependencies.rs View lib/issues/src/handlers/dependencies/list_dependencies.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/dependencies",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/dependencies",
1414 tag = "issues",
1515 params(IssuePath),
1616 responses(
Modified lib/issues/src/handlers/events/list_events.rs View lib/issues/src/handlers/events/list_events.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/events",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/events",
1313 tag = "issues",
1414 params(EventPath, ListEventQuery),
1515 responses(
Modified lib/issues/src/handlers/issue_users/list_issue_users.rs View lib/issues/src/handlers/issue_users/list_issue_users.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/users",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/users",
1414 tag = "issues",
1515 params(IssuePath, ListIssueUserQuery),
1616 responses(
Modified lib/issues/src/handlers/issue_users/update_issue_user.rs View lib/issues/src/handlers/issue_users/update_issue_user.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 patch,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/users/{uid}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/users/{user_id}",
1313 tag = "issues",
1414 params(IssueUserPath),
1515 request_body = UpdateIssueUserCmd,
@@ -32,7 +32,7 @@
3232 &path.space_name,
3333 path.board_id,
3434 path.issue_id,
35 path.uid,
35 path.user_id,
3636 cmd,
3737 )
3838 .await?;
Modified lib/issues/src/handlers/issues/close_issue.rs View lib/issues/src/handlers/issues/close_issue.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/close",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/close",
1313 tag = "issues",
1414 params(IssuePath),
1515 responses(
Modified lib/issues/src/handlers/issues/create_issue.rs View lib/issues/src/handlers/issues/create_issue.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 post,
13 path = "/issues/{space_name}/{board_id}/issues",
13 path = "/issues/{space_name}/boards/{board_id}/issues",
1414 tag = "issues",
1515 params(BoardPath),
1616 request_body = CreateIssueCmd,
Modified lib/issues/src/handlers/issues/delete_issue.rs View lib/issues/src/handlers/issues/delete_issue.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}/{issue_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}",
1313 tag = "issues",
1414 params(IssuePath),
1515 responses(
Modified lib/issues/src/handlers/issues/get_issue.rs View lib/issues/src/handlers/issues/get_issue.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{board_id}/{issue_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}",
1313 tag = "issues",
1414 params(IssuePath),
1515 responses(
Modified lib/issues/src/handlers/issues/list_issues.rs View lib/issues/src/handlers/issues/list_issues.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}/{board_id}/issues",
13 path = "/issues/{space_name}/boards/{board_id}/issues",
1414 tag = "issues",
1515 params(BoardPath, ListIssueQuery),
1616 responses(
Modified lib/issues/src/handlers/issues/reopen_issue.rs View lib/issues/src/handlers/issues/reopen_issue.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/reopen",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/reopen",
1313 tag = "issues",
1414 params(IssuePath),
1515 responses(
Modified lib/issues/src/handlers/issues/update_issue.rs View lib/issues/src/handlers/issues/update_issue.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 patch,
12 path = "/issues/{space_name}/{board_id}/{issue_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}",
1313 tag = "issues",
1414 params(IssuePath),
1515 request_body = UpdateIssueCmd,
Modified lib/issues/src/handlers/labels/bind_label.rs View lib/issues/src/handlers/labels/bind_label.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/labels/{label_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/labels/{label_id}",
1313 tag = "issues",
1414 params(IssueLabelPath),
1515 responses(
Modified lib/issues/src/handlers/labels/create_label.rs View lib/issues/src/handlers/labels/create_label.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 post,
13 path = "/issues/{space_name}/{board_id}/labels",
13 path = "/issues/{space_name}/boards/{board_id}/labels",
1414 tag = "issues",
1515 params(BoardPath),
1616 request_body = CreateLabelCmd,
Modified lib/issues/src/handlers/labels/delete_label.rs View lib/issues/src/handlers/labels/delete_label.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}/labels/{label_id}",
12 path = "/issues/{space_name}/boards/{board_id}/labels/{label_id}",
1313 tag = "issues",
1414 params(LabelPath),
1515 responses(
Modified lib/issues/src/handlers/labels/get_label.rs View lib/issues/src/handlers/labels/get_label.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{board_id}/labels/{label_id}",
12 path = "/issues/{space_name}/boards/{board_id}/labels/{label_id}",
1313 tag = "issues",
1414 params(LabelPath),
1515 responses(
Modified lib/issues/src/handlers/labels/list_labels.rs View lib/issues/src/handlers/labels/list_labels.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}/{board_id}/labels",
13 path = "/issues/{space_name}/boards/{board_id}/labels",
1414 tag = "issues",
1515 params(BoardPath, ListLabelQuery),
1616 responses(
Modified lib/issues/src/handlers/labels/list_labels_for_issue.rs View lib/issues/src/handlers/labels/list_labels_for_issue.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/labels",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/labels",
1414 tag = "issues",
1515 params(IssuePath),
1616 responses(
Modified lib/issues/src/handlers/labels/unbind_label.rs View lib/issues/src/handlers/labels/unbind_label.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/labels/{label_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/labels/{label_id}",
1313 tag = "issues",
1414 params(IssueLabelPath),
1515 responses(
Modified lib/issues/src/handlers/labels/update_label.rs View lib/issues/src/handlers/labels/update_label.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 patch,
12 path = "/issues/{space_name}/{board_id}/labels/{label_id}",
12 path = "/issues/{space_name}/boards/{board_id}/labels/{label_id}",
1313 tag = "issues",
1414 params(LabelPath),
1515 request_body = UpdateLabelCmd,
Modified lib/issues/src/handlers/members/create_member.rs View lib/issues/src/handlers/members/create_member.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 post,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/members",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/members",
1414 tag = "issues",
1515 params(IssuePath),
1616 request_body = CreateIssueMemberCmd,
Modified lib/issues/src/handlers/members/delete_member.rs View lib/issues/src/handlers/members/delete_member.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/members/{member_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/members/{member_id}",
1313 tag = "issues",
1414 params(IssueMemberPath),
1515 responses(
Modified lib/issues/src/handlers/members/list_members.rs View lib/issues/src/handlers/members/list_members.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/members",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/members",
1414 tag = "issues",
1515 params(IssuePath, ListIssueMemberQuery),
1616 responses(
Modified lib/issues/src/handlers/milestones/create_milestone.rs View lib/issues/src/handlers/milestones/create_milestone.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 post,
13 path = "/issues/{space_name}/{board_id}/milestones",
13 path = "/issues/{space_name}/boards/{board_id}/milestones",
1414 tag = "issues",
1515 params(BoardPath),
1616 request_body = CreateMilestoneCmd,
Modified lib/issues/src/handlers/milestones/delete_milestone.rs View lib/issues/src/handlers/milestones/delete_milestone.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}/milestones/{milestone_id}",
12 path = "/issues/{space_name}/boards/{board_id}/milestones/{milestone_id}",
1313 tag = "issues",
1414 params(MilestonePath),
1515 responses(
Modified lib/issues/src/handlers/milestones/get_milestone.rs View lib/issues/src/handlers/milestones/get_milestone.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{board_id}/milestones/{milestone_id}",
12 path = "/issues/{space_name}/boards/{board_id}/milestones/{milestone_id}",
1313 tag = "issues",
1414 params(MilestonePath),
1515 responses(
Modified lib/issues/src/handlers/milestones/list_milestones.rs View lib/issues/src/handlers/milestones/list_milestones.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}/{board_id}/milestones",
13 path = "/issues/{space_name}/boards/{board_id}/milestones",
1414 tag = "issues",
1515 params(BoardPath, ListMilestoneQuery),
1616 responses(
Modified lib/issues/src/handlers/milestones/update_milestone.rs View lib/issues/src/handlers/milestones/update_milestone.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 patch,
12 path = "/issues/{space_name}/{board_id}/milestones/{milestone_id}",
12 path = "/issues/{space_name}/boards/{board_id}/milestones/{milestone_id}",
1313 tag = "issues",
1414 params(MilestonePath),
1515 request_body = UpdateMilestoneCmd,
Modified lib/issues/src/handlers/pins/create_pin.rs View lib/issues/src/handlers/pins/create_pin.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 post,
13 path = "/issues/{space_name}/{board_id}/pins",
13 path = "/issues/{space_name}/boards/{board_id}/pins",
1414 tag = "issues",
1515 params(BoardPath),
1616 request_body = CreatePinCmd,
Modified lib/issues/src/handlers/pins/delete_pin.rs View lib/issues/src/handlers/pins/delete_pin.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}/pins/{pin_id}",
12 path = "/issues/{space_name}/boards/{board_id}/pins/{pin_id}",
1313 tag = "issues",
1414 params(PinPath),
1515 responses(
Modified lib/issues/src/handlers/pins/list_pins.rs View lib/issues/src/handlers/pins/list_pins.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}/{board_id}/pins",
13 path = "/issues/{space_name}/boards/{board_id}/pins",
1414 tag = "issues",
1515 params(BoardPath),
1616 responses(
Modified lib/issues/src/handlers/reactions/create_comment_reaction.rs View lib/issues/src/handlers/reactions/create_comment_reaction.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/comments/{comment_id}/reactions",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/comments/{comment_id}/reactions",
1313 tag = "issues",
1414 params(CommentReactionPath),
1515 request_body = CreateReactionCmd,
Modified lib/issues/src/handlers/reactions/create_reaction.rs View lib/issues/src/handlers/reactions/create_reaction.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 post,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/reactions",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/reactions",
1414 tag = "issues",
1515 params(IssuePath),
1616 request_body = CreateReactionCmd,
Modified lib/issues/src/handlers/reactions/delete_reaction.rs View lib/issues/src/handlers/reactions/delete_reaction.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/reactions/{reaction_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/reactions/{reaction_id}",
1313 tag = "issues",
1414 params(ReactionPath),
1515 responses(
Modified lib/issues/src/handlers/reactions/list_comment_reactions.rs View lib/issues/src/handlers/reactions/list_comment_reactions.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/comments/{comment_id}/reactions",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/comments/{comment_id}/reactions",
1313 tag = "issues",
1414 params(CommentReactionPath),
1515 responses(
Modified lib/issues/src/handlers/reactions/list_reactions.rs View lib/issues/src/handlers/reactions/list_reactions.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/reactions",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/reactions",
1414 tag = "issues",
1515 params(IssuePath),
1616 responses(
Modified lib/issues/src/handlers/repo_relations/create_repo_relation.rs View lib/issues/src/handlers/repo_relations/create_repo_relation.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 post,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/repos",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/repos",
1414 tag = "issues",
1515 params(IssuePath),
1616 request_body = CreateRepoRelCmd,
Modified lib/issues/src/handlers/repo_relations/delete_repo_relation.rs View lib/issues/src/handlers/repo_relations/delete_repo_relation.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/repos/{rel_id}",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/repos/{rel_id}",
1313 tag = "issues",
1414 params(RepoRelPath),
1515 responses(
Modified lib/issues/src/handlers/repo_relations/list_repo_relations.rs View lib/issues/src/handlers/repo_relations/list_repo_relations.rs
@@ -10,7 +10,7 @@
1010
1111#[utoipa::path(
1212 get,
13 path = "/issues/{space_name}/{board_id}/{issue_id}/repos",
13 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/repos",
1414 tag = "issues",
1515 params(IssuePath),
1616 responses(
Modified lib/issues/src/handlers/stopwatch/get_stopwatch.rs View lib/issues/src/handlers/stopwatch/get_stopwatch.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/stopwatch",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/stopwatch",
1313 tag = "issues",
1414 params(StopwatchPath),
1515 responses(
Modified lib/issues/src/handlers/stopwatch/start_stopwatch.rs View lib/issues/src/handlers/stopwatch/start_stopwatch.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/stopwatch/start",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/stopwatch/start",
1313 tag = "issues",
1414 params(StopwatchPath),
1515 responses(
Modified lib/issues/src/handlers/stopwatch/stop_stopwatch.rs View lib/issues/src/handlers/stopwatch/stop_stopwatch.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 post,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/stopwatch/stop",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/stopwatch/stop",
1313 tag = "issues",
1414 params(StopwatchPath),
1515 responses(
Modified lib/issues/src/handlers/tracked_time/list_tracked_time.rs View lib/issues/src/handlers/tracked_time/list_tracked_time.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/times",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/times",
1313 tag = "issues",
1414 params(TrackedTimePath, ListTrackedTimeQuery),
1515 responses(
Modified lib/issues/src/handlers/watches/delete_watch.rs View lib/issues/src/handlers/watches/delete_watch.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 delete,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/watch",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/watch",
1313 tag = "issues",
1414 params(WatchPath),
1515 responses(
Modified lib/issues/src/handlers/watches/get_watch.rs View lib/issues/src/handlers/watches/get_watch.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 get,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/watch",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/watch",
1313 tag = "issues",
1414 params(WatchPath),
1515 responses(
Modified lib/issues/src/handlers/watches/upsert_watch.rs View lib/issues/src/handlers/watches/upsert_watch.rs
@@ -9,7 +9,7 @@
99
1010#[utoipa::path(
1111 put,
12 path = "/issues/{space_name}/{board_id}/{issue_id}/watch",
12 path = "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/watch",
1313 tag = "issues",
1414 params(WatchPath),
1515 request_body = UpsertWatchCmd,
Modified lib/issues/src/services/issue_users.rs View lib/issues/src/services/issue_users.rs
@@ -46,7 +46,7 @@
4646 pub space_name: String,
4747 pub board_id: Uuid,
4848 pub issue_id: Uuid,
49 pub uid: Uuid,
49 pub user_id: Uuid,
5050}
5151
5252impl IssueService {
Modified lib/issues/src/errors.rs View lib/issues/src/errors.rs
@@ -138,11 +138,11 @@
138138impl From<IssueError> for ApiError {
139139 fn from(error: IssueError) -> Self {
140140 match error {
141 IssueError::BadRequest(msg) => Self::BadRequest(AppResponseData::err(400, msg)),
142 IssueError::Unauthorized => Self::Unauthorized(Self::payload(401, "unauthorized")),
143 IssueError::Forbidden => Self::Forbidden(Self::payload(403, "forbidden")),
144 IssueError::NotFound => Self::NotFound(Self::payload(404, "not found")),
145 IssueError::Conflict(msg) => Self::Conflict(AppResponseData::err(409, msg)),
141 IssueError::BadRequest(msg) => Self::BadRequest(AppResponseData::err(400, msg).with_error_code("bad_request")),
142 IssueError::Unauthorized => Self::Unauthorized(Self::payload(401, "unauthorized").with_error_code("unauthorized")),
143 IssueError::Forbidden => Self::Forbidden(Self::payload(403, "forbidden").with_error_code("forbidden")),
144 IssueError::NotFound => Self::NotFound(Self::payload(404, "not found").with_error_code("not_found")),
145 IssueError::Conflict(msg) => Self::Conflict(AppResponseData::err(409, msg).with_error_code("conflict")),
146146 IssueError::Internal(message) => {
147147 tracing::warn!(error = %message, "issues handler failed");
148148 Self::Internal(Self::payload(500, "internal server error"))
Modified lib/issues/src/routes.rs View lib/issues/src/routes.rs
@@ -64,141 +64,141 @@
6464
6565pub fn router(service: IssueService) -> Router<IssueService> {
6666 Router::new()
67 .route("/issues/{space_name}", post(create_board).get(list_boards))
67 .route("/issues/{space_name}/boards", post(create_board).get(list_boards))
6868 .route(
69 "/issues/{space_name}/{board_id}",
69 "/issues/{space_name}/boards/{board_id}",
7070 get(get_board).patch(update_board).delete(delete_board),
7171 )
7272 .route(
73 "/issues/{space_name}/{board_id}/issues",
73 "/issues/{space_name}/boards/{board_id}/issues",
7474 get(list_issues).post(create_issue),
7575 )
7676 .route(
77 "/issues/{space_name}/{board_id}/{issue_id}",
77 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}",
7878 get(get_issue).patch(update_issue).delete(delete_issue),
7979 )
8080 .route(
81 "/issues/{space_name}/{board_id}/{issue_id}/close",
81 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/close",
8282 post(close_issue),
8383 )
8484 .route(
85 "/issues/{space_name}/{board_id}/{issue_id}/reopen",
85 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/reopen",
8686 post(reopen_issue),
8787 )
8888 .route(
89 "/issues/{space_name}/{board_id}/{issue_id}/comments",
89 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/comments",
9090 get(list_comments).post(create_comment),
9191 )
9292 .route(
93 "/issues/{space_name}/{board_id}/{issue_id}/comments/{comment_id}",
93 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/comments/{comment_id}",
9494 get(get_comment)
9595 .patch(update_comment)
9696 .delete(delete_comment),
9797 )
9898 .route(
99 "/issues/{space_name}/{board_id}/labels",
99 "/issues/{space_name}/boards/{board_id}/labels",
100100 get(list_labels).post(create_label),
101101 )
102102 .route(
103 "/issues/{space_name}/{board_id}/labels/{label_id}",
103 "/issues/{space_name}/boards/{board_id}/labels/{label_id}",
104104 get(get_label).patch(update_label).delete(delete_label),
105105 )
106106 .route(
107 "/issues/{space_name}/{board_id}/{issue_id}/labels",
107 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/labels",
108108 get(list_labels_for_issue),
109109 )
110110 .route(
111 "/issues/{space_name}/{board_id}/{issue_id}/labels/{label_id}",
111 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/labels/{label_id}",
112112 post(bind_label).delete(unbind_label),
113113 )
114114 .route(
115 "/issues/{space_name}/{board_id}/milestones",
115 "/issues/{space_name}/boards/{board_id}/milestones",
116116 get(list_milestones).post(create_milestone),
117117 )
118118 .route(
119 "/issues/{space_name}/{board_id}/milestones/{milestone_id}",
119 "/issues/{space_name}/boards/{board_id}/milestones/{milestone_id}",
120120 get(get_milestone)
121121 .patch(update_milestone)
122122 .delete(delete_milestone),
123123 )
124124 .route(
125 "/issues/{space_name}/{board_id}/{issue_id}/reactions",
125 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/reactions",
126126 get(list_reactions).post(create_reaction),
127127 )
128128 .route(
129 "/issues/{space_name}/{board_id}/{issue_id}/reactions/{reaction_id}",
129 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/reactions/{reaction_id}",
130130 delete(delete_reaction),
131131 )
132132 .route(
133 "/issues/{space_name}/{board_id}/{issue_id}/comments/{comment_id}/reactions",
133 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/comments/{comment_id}/reactions",
134134 get(list_comment_reactions).post(create_comment_reaction),
135135 )
136136 .route(
137 "/issues/{space_name}/{board_id}/{issue_id}/watch",
137 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/watch",
138138 get(get_watch).put(upsert_watch).delete(delete_watch),
139139 )
140140 .route(
141 "/issues/{space_name}/{board_id}/{issue_id}/members",
141 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/members",
142142 get(list_members).post(create_member),
143143 )
144144 .route(
145 "/issues/{space_name}/{board_id}/{issue_id}/members/{member_id}",
145 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/members/{member_id}",
146146 delete(delete_member),
147147 )
148148 .route(
149 "/issues/{space_name}/{board_id}/pins",
149 "/issues/{space_name}/boards/{board_id}/pins",
150150 get(list_pins).post(create_pin),
151151 )
152152 .route(
153 "/issues/{space_name}/{board_id}/pins/{pin_id}",
153 "/issues/{space_name}/boards/{board_id}/pins/{pin_id}",
154154 delete(delete_pin),
155155 )
156156 .route(
157 "/issues/{space_name}/{board_id}/{issue_id}/events",
157 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/events",
158158 get(list_events),
159159 )
160160 .route(
161 "/issues/{space_name}/{board_id}/{issue_id}/dependencies",
161 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/dependencies",
162162 get(list_dependencies).post(create_dependency),
163163 )
164164 .route(
165 "/issues/{space_name}/{board_id}/{issue_id}/dependencies/{dep_id}",
165 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/dependencies/{dep_id}",
166166 delete(delete_dependency),
167167 )
168168 .route(
169 "/issues/{space_name}/{board_id}/{issue_id}/history",
169 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/history",
170170 get(list_content_history),
171171 )
172172 .route(
173 "/issues/{space_name}/{board_id}/{issue_id}/stopwatch",
173 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/stopwatch",
174174 get(get_stopwatch),
175175 )
176176 .route(
177 "/issues/{space_name}/{board_id}/{issue_id}/stopwatch/start",
177 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/stopwatch/start",
178178 post(start_stopwatch),
179179 )
180180 .route(
181 "/issues/{space_name}/{board_id}/{issue_id}/stopwatch/stop",
181 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/stopwatch/stop",
182182 post(stop_stopwatch),
183183 )
184184 .route(
185 "/issues/{space_name}/{board_id}/{issue_id}/times",
185 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/times",
186186 get(list_tracked_time),
187187 )
188188 .route(
189 "/issues/{space_name}/{board_id}/{issue_id}/users",
189 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/users",
190190 get(list_issue_users),
191191 )
192192 .route(
193 "/issues/{space_name}/{board_id}/{issue_id}/users/{uid}",
193 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/users/{user_id}",
194194 patch(update_issue_user),
195195 )
196196 .route(
197 "/issues/{space_name}/{board_id}/{issue_id}/repos",
197 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/repos",
198198 get(list_repo_relations).post(create_repo_relation),
199199 )
200200 .route(
201 "/issues/{space_name}/{board_id}/{issue_id}/repos/{rel_id}",
201 "/issues/{space_name}/boards/{board_id}/issues/{issue_id}/repos/{rel_id}",
202202 delete(delete_repo_relation),
203203 )
204204 .route_layer(axum::middleware::from_fn_with_state(
Modified lib/space/src/services/spaces.rs View lib/space/src/services/spaces.rs
@@ -115,17 +115,9 @@
115115 pub welcome_object: Option<Option<Uuid>>,
116116}
117117
118#[derive(Debug, Clone, Serialize, ToSchema)]
119pub struct SpacePage {
120 pub items: Vec<SpaceDto>,
121 pub total: i64,
122}
118pub type SpacePage = export::pagination::Page<SpaceDto>;
123119
124#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
125pub struct PageQuery {
126 pub limit: Option<u64>,
127 pub offset: Option<u64>,
128}
120pub use export::pagination::PageQuery;
129121
130122#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
131123pub struct SpaceNamePath {
@@ -513,6 +505,11 @@
513505 })
514506 .collect();
515507
516 Ok(SpacePage { items, total })
508 Ok(SpacePage {
509 items,
510 total,
511 limit,
512 offset,
513 })
517514 }
518515}
Modified lib/space/src/errors.rs View lib/space/src/errors.rs
@@ -131,11 +131,11 @@
131131impl From<SpaceError> for ApiError {
132132 fn from(error: SpaceError) -> Self {
133133 match error {
134 SpaceError::BadRequest(msg) => Self::BadRequest(AppResponseData::err(400, msg)),
135 SpaceError::Unauthorized => Self::Unauthorized(Self::payload(401, "unauthorized")),
136 SpaceError::Forbidden => Self::Forbidden(Self::payload(403, "forbidden")),
137 SpaceError::NotFound => Self::NotFound(Self::payload(404, "not found")),
138 SpaceError::Conflict(msg) => Self::Conflict(AppResponseData::err(409, msg)),
134 SpaceError::BadRequest(msg) => Self::BadRequest(AppResponseData::err(400, msg).with_error_code("bad_request")),
135 SpaceError::Unauthorized => Self::Unauthorized(Self::payload(401, "unauthorized").with_error_code("unauthorized")),
136 SpaceError::Forbidden => Self::Forbidden(Self::payload(403, "forbidden").with_error_code("forbidden")),
137 SpaceError::NotFound => Self::NotFound(Self::payload(404, "not found").with_error_code("not_found")),
138 SpaceError::Conflict(msg) => Self::Conflict(AppResponseData::err(409, msg).with_error_code("conflict")),
139139 SpaceError::Internal(message) => {
140140 tracing::warn!(error = %message, "space handler failed");
141141 Self::Internal(Self::payload(500, "internal server error"))
Modified lib/space/src/openapi.rs View lib/space/src/openapi.rs
@@ -78,7 +78,7 @@
7878 AppResponseData<crate::services::dir::UserOverwriteDto>,
7979 AppResponseData<Vec<crate::services::dir::UserOverwriteDto>>,
8080 crate::services::spaces::SpaceDto,
81 crate::services::spaces::SpacePage,
81 export::pagination::Page<crate::services::spaces::SpaceDto>,
8282 crate::services::spaces::CreateSpaceCmd,
8383 crate::services::spaces::UpdateSpaceCmd,
8484 crate::services::spaces::UserBriefDto,
Modified lib/user/src/share/page.rs View lib/user/src/share/page.rs
@@ -1,23 +1 @@
1use serde::Deserialize;
2use utoipa::{IntoParams, ToSchema};
3
4pub const DEFAULT_PAGE_LIMIT: u64 = 50;
5pub const MAX_PAGE_LIMIT: u64 = 100;
6
7#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
8pub struct PageQuery {
9 pub limit: Option<u64>,
10 pub offset: Option<u64>,
11}
12
13impl PageQuery {
14 pub fn limit(&self) -> u64 {
15 self.limit
16 .unwrap_or(DEFAULT_PAGE_LIMIT)
17 .clamp(1, MAX_PAGE_LIMIT)
18 }
19
20 pub fn offset(&self) -> u64 {
21 self.offset.unwrap_or(0)
22 }
23}
1pub use export::pagination::{PageQuery, Page, DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT};
Modified lib/user/src/share/types.rs View lib/user/src/share/types.rs
@@ -1,5 +1,5 @@
11use serde::{Deserialize, Serialize};
2use utoipa::{IntoParams, ToSchema};
2use utoipa::ToSchema;
33use uuid::Uuid;
44
55#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
@@ -10,16 +10,4 @@
1010 pub avatar_url: String,
1111}
1212
13#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, IntoParams)]
14pub struct ListQuery {
15 pub limit: Option<u64>,
16 pub offset: Option<u64>,
17}
18
19impl ListQuery {
20 pub fn page(&self) -> (u64, u64) {
21 let limit = self.limit.unwrap_or(50).clamp(1, 100);
22 let offset = self.offset.unwrap_or(0);
23 (limit, offset)
24 }
25}
13pub type ListQuery = export::pagination::PageQuery;
Modified lib/user/src/errors.rs View lib/user/src/errors.rs
@@ -137,11 +137,11 @@
137137impl From<UserError> for ApiError {
138138 fn from(error: UserError) -> Self {
139139 match error {
140 UserError::BadRequest(msg) => Self::BadRequest(AppResponseData::err(400, msg)),
141 UserError::Unauthorized => Self::Unauthorized(Self::payload(401, "unauthorized")),
142 UserError::Forbidden => Self::Forbidden(Self::payload(403, "forbidden")),
143 UserError::NotFound => Self::NotFound(Self::payload(404, "not found")),
144 UserError::Conflict(msg) => Self::Conflict(AppResponseData::err(409, msg)),
140 UserError::BadRequest(msg) => Self::BadRequest(AppResponseData::err(400, msg).with_error_code("bad_request")),
141 UserError::Unauthorized => Self::Unauthorized(Self::payload(401, "unauthorized").with_error_code("unauthorized")),
142 UserError::Forbidden => Self::Forbidden(Self::payload(403, "forbidden").with_error_code("forbidden")),
143 UserError::NotFound => Self::NotFound(Self::payload(404, "not found").with_error_code("not_found")),
144 UserError::Conflict(msg) => Self::Conflict(AppResponseData::err(409, msg).with_error_code("conflict")),
145145 UserError::Internal(message) => {
146146 tracing::warn!(error = %message, "user handler failed");
147147 Self::Internal(Self::payload(500, "internal server error"))
Modified lib/webapi/src/lib.rs View lib/webapi/src/lib.rs
@@ -1,13 +1,15 @@
1pub mod metrics;
12pub mod openapi;
23
34use axum::Router;
45use export::AppState;
6use export::middleware::AppTraceLayer;
57use session::{SessionLayer, store};
68use tower_http::limit::RequestBodyLimitLayer;
79use tower_http::trace::{DefaultMakeSpan, DefaultOnRequest, DefaultOnResponse, TraceLayer};
810use tracing::Level;
911
10/// Max chat JSON request body (covers the 512 KiB base64 emoji upload).
12
1113const MAX_REQUEST_BODY_BYTES: usize = 2 * 1024 * 1024;
1214
1315pub async fn router(state: AppState) -> Result<Router, axum_session::SessionError> {
@@ -20,13 +22,24 @@
2022 let user = user::services::service::UserService::new(state.clone());
2123 let git = git::service::GitService::new(state);
2224
23 let router: Router = Router::new()
24 .merge(auth::routes::router(auth.clone()).with_state(auth))
25 let public: Router = Router::new()
26 .merge(auth::routes::router(auth.clone()).with_state(auth.clone()))
2527 .merge(chat::routes::router(chat.clone()).with_state(chat))
2628 .merge(issues::routes::router(issues.clone()).with_state(issues))
2729 .merge(space::routes::router(space.clone()).with_state(space))
2830 .merge(user::routes::router(user.clone()).with_state(user))
29 .merge(git::routes::router(git.clone()).with_state(git))
31 .merge(git::routes::router(git.clone()).with_state(git.clone()));
32 let internal: Router = Router::new()
33 .merge(git::routes::internal_router(git.clone()).with_state(git))
34 .route_layer(axum::middleware::from_fn_with_state(
35 auth.clone(),
36 auth::share::guard::admin_guard,
37 ));
38
39 let router: Router = Router::new()
40 .nest("/api/v1", public)
41 .nest("/api/v1/internal", internal)
42 .nest("/pages", auth::routes::pages_router(auth.clone()))
3043 .layer(SessionLayer::new(store))
3144 .layer(RequestBodyLimitLayer::new(MAX_REQUEST_BODY_BYTES))
3245 .layer(
@@ -38,7 +51,9 @@
3851 )
3952 .on_request(DefaultOnRequest::new().level(Level::INFO))
4053 .on_response(DefaultOnResponse::new().level(Level::INFO)),
41 );
54 )
55 .layer(metrics::metrics_layer())
56 .layer(AppTraceLayer::new());
4257
4358 Ok(router)
4459}
Added lib/webapi/src/metrics.rs View lib/webapi/src/metrics.rs
@@ -0,0 +1,28 @@
1use std::time::Duration;
2
3use axum_otel_metrics::{HttpMetricsLayer, HttpMetricsLayerBuilder};
4use opentelemetry::global;
5use opentelemetry_otlp::MetricExporter;
6use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider, Temporality};
7
8const EXPORT_INTERVAL: Duration = Duration::from_secs(30);
9
10pub fn init_meter_provider() -> Result<SdkMeterProvider, String> {
11 let exporter = MetricExporter::builder()
12 .with_http()
13 .with_temporality(Temporality::default())
14 .build()
15 .map_err(|e| e.to_string())?;
16
17 let reader = PeriodicReader::builder(exporter)
18 .with_interval(EXPORT_INTERVAL)
19 .build();
20
21 let provider = SdkMeterProvider::builder().with_reader(reader).build();
22 global::set_meter_provider(provider.clone());
23 Ok(provider)
24}
25
26pub fn metrics_layer() -> HttpMetricsLayer {
27 HttpMetricsLayerBuilder::new().build()
28}
Modified lib/webapi/src/openapi.rs View lib/webapi/src/openapi.rs
@@ -12,5 +12,6 @@
1212 doc.merge(space::openapi::SpaceApiDoc::openapi());
1313 doc.merge(user::openapi::UserApiDoc::openapi());
1414 doc.merge(git::openapi::GitApiDoc::openapi());
15 doc.servers = Some(vec![utoipa::openapi::Server::new("/api/v1")]);
1516 doc
1617}
Modified lib/webapi/Cargo.toml View lib/webapi/Cargo.toml
@@ -16,4 +16,8 @@
1616axum_session = { workspace = true }
1717tower-http = { workspace = true }
1818tracing = { workspace = true }
19utoipa = { workspace = true }
No newline at end of file
19utoipa = { workspace = true }
20axum-otel-metrics = { workspace = true }
21opentelemetry = { workspace = true }
22opentelemetry_sdk = { workspace = true }
23opentelemetry-otlp = { workspace = true }
No newline at end of file
Modified Cargo.lock View Cargo.lock
@@ -716,6 +716,20 @@
716716]
717717
718718[[package]]
719name = "axum-otel-metrics"
720version = "0.14.1"
721source = "registry+https://github.com/rust-lang/crates.io-index"
722checksum = "d0d74d40b1ff9bd3e8f782aa6798d22ff6a7d1b53049e0e239f5332dab2dd83d"
723dependencies = [
724 "axum",
725 "http-body 1.1.0",
726 "opentelemetry",
727 "opentelemetry-semantic-conventions",
728 "pin-project-lite",
729 "tower",
730]
731
732[[package]]
719733name = "axum_session"
720734version = "0.21.0"
721735source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2043,15 +2057,21 @@
20432057version = "1.0.0"
20442058dependencies = [
20452059 "axum",
2060 "bytes",
20462061 "config",
20472062 "email",
2063 "http-body-util",
2064 "rand 0.8.7",
20482065 "redis",
20492066 "sea-orm",
20502067 "serde",
2068 "serde_json",
20512069 "storage",
20522070 "tokio",
2071 "tower",
20532072 "tracing",
20542073 "utoipa",
2074 "uuid",
20552075]
20562076
20572077[[package]]
@@ -3925,6 +3945,7 @@
39253945 "pin-project-lite",
39263946 "smallvec",
39273947 "tokio",
3948 "want",
39283949]
39293950
39303951[[package]]
@@ -3957,13 +3978,21 @@
39573978source = "registry+https://github.com/rust-lang/crates.io-index"
39583979checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
39593980dependencies = [
3981 "base64 0.22.1",
39603982 "bytes",
3983 "futures-channel",
3984 "futures-util",
39613985 "http 1.5.0",
39623986 "http-body 1.1.0",
39633987 "hyper 1.11.0",
3988 "ipnet",
3989 "libc",
3990 "percent-encoding 2.3.2",
39643991 "pin-project-lite",
3992 "socket2 0.6.5",
39653993 "tokio",
39663994 "tower-service",
3995 "tracing",
39673996]
39683997
39693998[[package]]
@@ -4530,6 +4559,12 @@
45304559]
45314560
45324561[[package]]
4562name = "ipnet"
4563version = "2.12.1"
4564source = "registry+https://github.com/rust-lang/crates.io-index"
4565checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
4566
4567[[package]]
45334568name = "issues"
45344569version = "1.0.0"
45354570dependencies = [
@@ -5400,6 +5435,84 @@
54005435]
54015436
54025437[[package]]
5438name = "opentelemetry"
5439version = "0.32.0"
5440source = "registry+https://github.com/rust-lang/crates.io-index"
5441checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682"
5442dependencies = [
5443 "futures-core",
5444 "futures-sink",
5445 "js-sys",
5446 "pin-project-lite",
5447 "thiserror 2.0.20",
5448 "tracing",
5449]
5450
5451[[package]]
5452name = "opentelemetry-http"
5453version = "0.32.0"
5454source = "registry+https://github.com/rust-lang/crates.io-index"
5455checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331"
5456dependencies = [
5457 "async-trait",
5458 "bytes",
5459 "http 1.5.0",
5460 "opentelemetry",
5461 "reqwest",
5462]
5463
5464[[package]]
5465name = "opentelemetry-otlp"
5466version = "0.32.0"
5467source = "registry+https://github.com/rust-lang/crates.io-index"
5468checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35"
5469dependencies = [
5470 "http 1.5.0",
5471 "opentelemetry",
5472 "opentelemetry-http",
5473 "opentelemetry-proto",
5474 "opentelemetry_sdk",
5475 "prost",
5476 "reqwest",
5477 "thiserror 2.0.20",
5478]
5479
5480[[package]]
5481name = "opentelemetry-proto"
5482version = "0.32.0"
5483source = "registry+https://github.com/rust-lang/crates.io-index"
5484checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638"
5485dependencies = [
5486 "opentelemetry",
5487 "opentelemetry_sdk",
5488 "prost",
5489]
5490
5491[[package]]
5492name = "opentelemetry-semantic-conventions"
5493version = "0.32.1"
5494source = "registry+https://github.com/rust-lang/crates.io-index"
5495checksum = "c913ac17a6c451661ee255f4625d143e51647ae78ebd969b75e41c4442f4fe47"
5496
5497[[package]]
5498name = "opentelemetry_sdk"
5499version = "0.32.1"
5500source = "registry+https://github.com/rust-lang/crates.io-index"
5501checksum = "9b59f80e1ac4d5ff7a2db8fb6c80badb7f0f3f858211fba08dd9aaec750894f9"
5502dependencies = [
5503 "futures-channel",
5504 "futures-executor",
5505 "futures-util",
5506 "opentelemetry",
5507 "percent-encoding 2.3.2",
5508 "portable-atomic",
5509 "rand 0.9.5",
5510 "thiserror 2.0.20",
5511 "tokio",
5512 "tokio-stream",
5513]
5514
5515[[package]]
54035516name = "option-ext"
54045517version = "0.2.0"
54055518source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6018,6 +6131,29 @@
60186131]
60196132
60206133[[package]]
6134name = "prost"
6135version = "0.14.4"
6136source = "registry+https://github.com/rust-lang/crates.io-index"
6137checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1"
6138dependencies = [
6139 "bytes",
6140 "prost-derive",
6141]
6142
6143[[package]]
6144name = "prost-derive"
6145version = "0.14.4"
6146source = "registry+https://github.com/rust-lang/crates.io-index"
6147checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf"
6148dependencies = [
6149 "anyhow",
6150 "itertools 0.14.0",
6151 "proc-macro2",
6152 "quote",
6153 "syn 2.0.119",
6154]
6155
6156[[package]]
60216157name = "psm"
60226158version = "0.1.32"
60236159source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6315,6 +6451,37 @@
63156451]
63166452
63176453[[package]]
6454name = "reqwest"
6455version = "0.13.4"
6456source = "registry+https://github.com/rust-lang/crates.io-index"
6457checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
6458dependencies = [
6459 "base64 0.22.1",
6460 "bytes",
6461 "futures-channel",
6462 "futures-core",
6463 "futures-util",
6464 "http 1.5.0",
6465 "http-body 1.1.0",
6466 "http-body-util",
6467 "hyper 1.11.0",
6468 "hyper-util",
6469 "js-sys",
6470 "log 0.4.33",
6471 "percent-encoding 2.3.2",
6472 "pin-project-lite",
6473 "sync_wrapper",
6474 "tokio",
6475 "tower",
6476 "tower-http 0.6.11",
6477 "tower-service",
6478 "url 2.5.8",
6479 "wasm-bindgen",
6480 "wasm-bindgen-futures",
6481 "web-sys",
6482]
6483
6484[[package]]
63186485name = "rfc6979"
63196486version = "0.4.0"
63206487source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7775,6 +7942,9 @@
77757942version = "1.0.2"
77767943source = "registry+https://github.com/rust-lang/crates.io-index"
77777944checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
7945dependencies = [
7946 "futures-core",
7947]
77787948
77797949[[package]]
77807950name = "synchost"
@@ -8126,6 +8296,24 @@
81268296
81278297[[package]]
81288298name = "tower-http"
8299version = "0.6.11"
8300source = "registry+https://github.com/rust-lang/crates.io-index"
8301checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
8302dependencies = [
8303 "bitflags 2.13.1",
8304 "bytes",
8305 "futures-util",
8306 "http 1.5.0",
8307 "http-body 1.1.0",
8308 "pin-project-lite",
8309 "tower",
8310 "tower-layer",
8311 "tower-service",
8312 "url 2.5.8",
8313]
8314
8315[[package]]
8316name = "tower-http"
81298317version = "0.7.0"
81308318source = "registry+https://github.com/rust-lang/crates.io-index"
81318319checksum = "b11f75e912b0c2be01b63d8cf8057b8c3f97cf34abb3d431a3a4c8675498e233"
@@ -8616,6 +8804,16 @@
86168804]
86178805
86188806[[package]]
8807name = "web-sys"
8808version = "0.3.104"
8809source = "registry+https://github.com/rust-lang/crates.io-index"
8810checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
8811dependencies = [
8812 "js-sys",
8813 "wasm-bindgen",
8814]
8815
8816[[package]]
86198817name = "web-time"
86208818version = "1.1.0"
86218819source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -8631,14 +8829,18 @@
86318829dependencies = [
86328830 "auth",
86338831 "axum",
8832 "axum-otel-metrics",
86348833 "axum_session",
86358834 "chat",
86368835 "export",
86378836 "git",
86388837 "issues",
8838 "opentelemetry",
8839 "opentelemetry-otlp",
8840 "opentelemetry_sdk",
86398841 "session",
86408842 "space",
8641 "tower-http",
8843 "tower-http 0.7.0",
86428844 "tracing",
86438845 "user",
86448846 "utoipa",
Modified Cargo.toml View Cargo.toml
@@ -55,7 +55,12 @@
5555serde_json = "1.0.151"
5656axum = { version = "0.8.9", features = ["ws"] }
5757axum_session = { version = "0.21.0", features = ["key-store"] }
58tower = "0.5.3"
5859tower-http = { version = "0.7.0", features = ["trace", "limit"] }
60axum-otel-metrics = "0.14.1"
61opentelemetry = "0.32"
62opentelemetry_sdk = { version = "0.32.1", features = ["rt-tokio"] }
63opentelemetry-otlp = "0.32"
5964tracing = "0.1.44"
6065figment = { version = "0.10", features = ["toml", "env"] }
6166dotenvy = "0.15"
Modified openapi.json View openapi.json

Diff too large to display.