Skip to content
Clone

docs: clear less

ZhenYi committed
parent 4dc6d37 commit bdce8a4 Browse files

300 files changed

+2183 −1390

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

File tree 300
Modified app/apihost/src/main.rs View app/apihost/src/main.rs
@@ -12,8 +12,7 @@
1212 init_tracing();
1313
1414 let cfg = config::AppConfig::load().context("loading configuration")?;
15 cfg.validate()
16 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
15 cfg.validate().map_err(|e| anyhow::anyhow!(e.to_string()))?;
1716 if !cfg.apihost.enabled() {
1817 info!("apihost is disabled by config; exiting");
1918 return Ok(());
@@ -29,9 +28,8 @@
2928 .await
3029 .map_err(|err| anyhow::anyhow!("failed to build router: {err}"))?;
3130 let doc = api_doc();
32 let api_router = api_router.merge(
33 SwaggerUi::new("/api/docs").url("/api/docs/openapi.json", doc),
34 );
31 let api_router =
32 api_router.merge(SwaggerUi::new("/api/docs").url("/api/docs/openapi.json", doc));
3533
3634 let bind_addr = cfg.apihost.bind_addr().to_string();
3735 let port = cfg.apihost.port();
@@ -55,8 +53,7 @@
5553
5654fn init_tracing() {
5755 use tracing_subscriber::EnvFilter;
58 let filter = EnvFilter::try_from_default_env()
59 .unwrap_or_else(|_| EnvFilter::new("info"));
56 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
6057 tracing_subscriber::fmt().with_env_filter(filter).init();
6158}
6259
@@ -69,7 +66,7 @@
6966
7067 #[cfg(unix)]
7168 let terminate = async {
72 use tokio::signal::unix::{signal, SignalKind};
69 use tokio::signal::unix::{SignalKind, signal};
7370 match signal(SignalKind::terminate()) {
7471 Ok(mut s) => {
7572 s.recv().await;
@@ -87,4 +84,4 @@
8784 _ = ctrl_c => info!("received SIGINT"),
8885 _ = terminate => info!("received SIGTERM"),
8986 }
90}
No newline at end of file
87}
Modified app/githost/src/main.rs View app/githost/src/main.rs
@@ -10,8 +10,7 @@
1010 init_tracing();
1111
1212 let cfg = config::AppConfig::load().context("loading configuration")?;
13 cfg.validate()
14 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
13 cfg.validate().map_err(|e| anyhow::anyhow!(e.to_string()))?;
1514 if !cfg.githost.enabled() {
1615 info!("githost is disabled by config; exiting");
1716 return Ok(());
@@ -82,8 +81,7 @@
8281
8382fn init_tracing() {
8483 use tracing_subscriber::EnvFilter;
85 let filter = EnvFilter::try_from_default_env()
86 .unwrap_or_else(|_| EnvFilter::new("info"));
84 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
8785 tracing_subscriber::fmt().with_env_filter(filter).init();
8886}
8987
@@ -114,4 +112,4 @@
114112 _ = ctrl_c => info!("received SIGINT"),
115113 _ = terminate => info!("received SIGTERM"),
116114 }
117}
No newline at end of file
115}
Modified app/mails/src/main.rs View app/mails/src/main.rs
@@ -1,7 +1,7 @@
11use std::time::Duration;
22
33use anyhow::Context;
4use email::runtime::{run_until_shutdown, WorkerOptions};
4use email::runtime::{WorkerOptions, run_until_shutdown};
55use tracing::info;
66
77#[tokio::main]
@@ -9,8 +9,7 @@
99 init_tracing();
1010
1111 let cfg = config::AppConfig::load().context("loading configuration")?;
12 cfg.validate()
13 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
12 cfg.validate().map_err(|e| anyhow::anyhow!(e.to_string()))?;
1413
1514 let redis_url = cfg.redis.url().to_string();
1615 let redis_host = match redis_url.rsplit_once('@') {
@@ -41,7 +40,6 @@
4140
4241fn init_tracing() {
4342 use tracing_subscriber::EnvFilter;
44 let filter = EnvFilter::try_from_default_env()
45 .unwrap_or_else(|_| EnvFilter::new("info"));
43 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
4644 tracing_subscriber::fmt().with_env_filter(filter).init();
47}
No newline at end of file
45}
Modified app/migrate/src/main.rs View app/migrate/src/main.rs
@@ -16,8 +16,7 @@
1616 .init();
1717
1818 let cfg = AppConfig::load().context("loading configuration")?;
19 cfg.validate()
20 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
19 cfg.validate().map_err(|e| anyhow::anyhow!(e.to_string()))?;
2120
2221 let opts = cfg
2322 .database
@@ -32,4 +31,4 @@
3231 info!("schema migration completed");
3332
3433 Ok(())
35}
No newline at end of file
34}
Modified app/synchost/src/main.rs View app/synchost/src/main.rs
@@ -12,8 +12,7 @@
1212 init_tracing();
1313
1414 let cfg = config::AppConfig::load().context("loading configuration")?;
15 cfg.validate()
16 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
15 cfg.validate().map_err(|e| anyhow::anyhow!(e.to_string()))?;
1716
1817 info!("connecting to database / redis / cache");
1918 let db = Arc::new(
@@ -72,8 +71,7 @@
7271
7372fn init_tracing() {
7473 use tracing_subscriber::EnvFilter;
75 let filter = EnvFilter::try_from_default_env()
76 .unwrap_or_else(|_| EnvFilter::new("info"));
74 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
7775 tracing_subscriber::fmt().with_env_filter(filter).init();
7876}
7977
@@ -86,7 +84,7 @@
8684
8785 #[cfg(unix)]
8886 let terminate = async {
89 use tokio::signal::unix::{signal, SignalKind};
87 use tokio::signal::unix::{SignalKind, signal};
9088 match signal(SignalKind::terminate()) {
9189 Ok(mut s) => {
9290 s.recv().await;
@@ -104,4 +102,4 @@
104102 _ = ctrl_c => info!("received SIGINT"),
105103 _ = terminate => info!("received SIGTERM"),
106104 }
107}
No newline at end of file
105}
Modified lib/auth/src/handlers/accept_agreement.rs View lib/auth/src/handlers/accept_agreement.rs
@@ -5,9 +5,9 @@
55use utoipa::ToSchema;
66
77use crate::errors::ApiError;
8use session::share::session::require_uid;
8use crate::services::AuthService;
99use crate::services::agreements::AcceptAgreementResp;
10use crate::services::AuthService;
10use session::share::session::require_uid;
1111
1212#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1313pub struct AcceptAgreementCmd {
@@ -39,4 +39,4 @@
3939 .await
4040 .map_err(ApiError::from)?;
4141 Ok(AppResponseData::ok(resp))
42}
No newline at end of file
42}
Modified lib/auth/src/handlers/active_suspension.rs View lib/auth/src/handlers/active_suspension.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::suspensions::SuspensionDto;
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 get,
@@ -25,4 +25,4 @@
2525 .await
2626 .map_err(ApiError::from)?;
2727 Ok(AppResponseData::ok(resp))
28}
No newline at end of file
28}
Modified lib/auth/src/handlers/add_ssh_key.rs View lib/auth/src/handlers/add_ssh_key.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::ssh_keys::{AddSshKeyCmd, SshKeyDto};
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 post,
@@ -27,4 +27,4 @@
2727 .await
2828 .map_err(ApiError::from)?;
2929 Ok(AppResponseData::ok(resp))
30}
No newline at end of file
30}
Modified lib/auth/src/handlers/agreement_page.rs View lib/auth/src/handlers/agreement_page.rs
@@ -9,10 +9,7 @@
99 State(service): State<AuthService>,
1010 Path(key): Path<String>,
1111) -> Result<Html<String>, ApiError> {
12 let doc = service
13 .list_agreements()
14 .into_iter()
15 .find(|d| d.key == key);
12 let doc = service.list_agreements().into_iter().find(|d| d.key == key);
1613 match doc {
1714 Some(doc) => {
1815 let html_content = templates::render_markdown(&doc.content_md);
Modified lib/auth/src/handlers/cancel_deletion.rs View lib/auth/src/handlers/cancel_deletion.rs
@@ -3,8 +3,8 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::AuthService;
67use session::share::session::require_uid;
7use crate::services::AuthService;
88
99#[utoipa::path(
1010 post,
@@ -21,4 +21,4 @@
2121 let uid = require_uid(&session)?;
2222 service.cancel_deletion(uid).await.map_err(ApiError::from)?;
2323 Ok(AppResponseData::empty())
24}
No newline at end of file
24}
Modified lib/auth/src/handlers/cancel_email_change.rs View lib/auth/src/handlers/cancel_email_change.rs
@@ -4,8 +4,8 @@
44use uuid::Uuid;
55
66use crate::errors::ApiError;
7use crate::services::AuthService;
78use session::share::session::require_uid;
8use crate::services::AuthService;
99
1010#[utoipa::path(
1111 delete,
@@ -29,4 +29,4 @@
2929 .await
3030 .map_err(ApiError::from)?;
3131 Ok(AppResponseData::empty())
32}
No newline at end of file
32}
Modified lib/auth/src/handlers/change_password.rs View lib/auth/src/handlers/change_password.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::password::ChangePasswordCmd;
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 post,
@@ -27,4 +27,4 @@
2727 .await
2828 .map_err(ApiError::from)?;
2929 Ok(AppResponseData::empty())
30}
No newline at end of file
30}
Modified lib/auth/src/handlers/confirm_email_change.rs View lib/auth/src/handlers/confirm_email_change.rs
@@ -6,8 +6,8 @@
66use uuid::Uuid;
77
88use crate::errors::ApiError;
9use crate::services::AuthService;
910use session::share::session::require_uid;
10use crate::services::AuthService;
1111
1212#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1313pub struct ConfirmEmailChangeCmd {
@@ -36,4 +36,4 @@
3636 .await
3737 .map_err(ApiError::from)?;
3838 Ok(AppResponseData::empty())
39}
No newline at end of file
39}
Modified lib/auth/src/handlers/create_access_key.rs View lib/auth/src/handlers/create_access_key.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::access_keys::{CreateAccessKeyCmd, CreateAccessKeyResp};
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 post,
@@ -27,4 +27,4 @@
2727 .await
2828 .map_err(ApiError::from)?;
2929 Ok(AppResponseData::ok(resp))
30}
No newline at end of file
30}
Modified lib/auth/src/handlers/create_report.rs View lib/auth/src/handlers/create_report.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::reports::{CreateReportCmd, ReportDto};
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 post,
@@ -27,4 +27,4 @@
2727 .await
2828 .map_err(ApiError::from)?;
2929 Ok(AppResponseData::ok(resp))
30}
No newline at end of file
30}
Modified lib/auth/src/handlers/delete_access_key.rs View lib/auth/src/handlers/delete_access_key.rs
@@ -4,8 +4,8 @@
44use uuid::Uuid;
55
66use crate::errors::ApiError;
7use crate::services::AuthService;
78use session::share::session::require_uid;
8use crate::services::AuthService;
99
1010#[utoipa::path(
1111 delete,
@@ -29,4 +29,4 @@
2929 .await
3030 .map_err(ApiError::from)?;
3131 Ok(AppResponseData::empty())
32}
No newline at end of file
32}
Modified lib/auth/src/handlers/delete_notification.rs View lib/auth/src/handlers/delete_notification.rs
@@ -4,8 +4,8 @@
44use uuid::Uuid;
55
66use crate::errors::ApiError;
7use crate::services::AuthService;
78use session::share::session::require_uid;
8use crate::services::AuthService;
99
1010#[utoipa::path(
1111 delete,
@@ -29,4 +29,4 @@
2929 .await
3030 .map_err(ApiError::from)?;
3131 Ok(AppResponseData::empty())
32}
No newline at end of file
32}
Modified lib/auth/src/handlers/delete_ssh_key.rs View lib/auth/src/handlers/delete_ssh_key.rs
@@ -4,8 +4,8 @@
44use uuid::Uuid;
55
66use crate::errors::ApiError;
7use crate::services::AuthService;
78use session::share::session::require_uid;
8use crate::services::AuthService;
99
1010#[utoipa::path(
1111 delete,
@@ -29,4 +29,4 @@
2929 .await
3030 .map_err(ApiError::from)?;
3131 Ok(AppResponseData::empty())
32}
No newline at end of file
32}
Modified lib/auth/src/handlers/deletion_status.rs View lib/auth/src/handlers/deletion_status.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::deletion::DeletionDto;
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 get,
@@ -22,4 +22,4 @@
2222 let uid = require_uid(&session)?;
2323 let resp = service.deletion_status(uid).await.map_err(ApiError::from)?;
2424 Ok(AppResponseData::ok(resp))
25}
No newline at end of file
25}
Modified lib/auth/src/handlers/forgot_password.rs View lib/auth/src/handlers/forgot_password.rs
@@ -3,8 +3,8 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::AuthService;
67use crate::services::password::{ForgotPasswordCmd, ForgotPasswordResp};
7use crate::services::AuthService;
88
99#[utoipa::path(
1010 post,
@@ -25,4 +25,4 @@
2525 .await
2626 .map_err(ApiError::from)?;
2727 Ok(AppResponseData::ok(resp))
28}
No newline at end of file
28}
Modified lib/auth/src/handlers/get_preferences.rs View lib/auth/src/handlers/get_preferences.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::preferences::UserPreferencesDto;
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 get,
@@ -22,4 +22,4 @@
2222 let uid = require_uid(&session)?;
2323 let resp = service.preferences(uid).await.map_err(ApiError::from)?;
2424 Ok(AppResponseData::ok(resp))
25}
No newline at end of file
25}
Modified lib/auth/src/handlers/get_user.rs View lib/auth/src/handlers/get_user.rs
@@ -3,8 +3,8 @@
33use uuid::Uuid;
44
55use crate::errors::ApiError;
6use crate::services::AuthService;
67use crate::share::types::UserPublic;
7use crate::services::AuthService;
88
99#[utoipa::path(
1010 get,
@@ -23,4 +23,4 @@
2323) -> Result<AppResponseData<UserPublic>, ApiError> {
2424 let user = service.public_user(uid).await.map_err(ApiError::from)?;
2525 Ok(AppResponseData::ok(user))
26}
No newline at end of file
26}
Modified lib/auth/src/handlers/list_access_keys.rs View lib/auth/src/handlers/list_access_keys.rs
@@ -3,10 +3,10 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
7use crate::share::types::ListQuery;
6use crate::services::AuthService;
87use crate::services::access_keys::AccessKeyDto;
9use crate::services::AuthService;
8use crate::share::types::ListQuery;
9use session::share::session::require_uid;
1010
1111#[utoipa::path(
1212 get,
@@ -23,6 +23,9 @@
2323 Query(query): Query<ListQuery>,
2424) -> Result<AppResponseData<Vec<AccessKeyDto>>, ApiError> {
2525 let uid = require_uid(&session)?;
26 let keys = service.list_access_keys(uid, query).await.map_err(ApiError::from)?;
26 let keys = service
27 .list_access_keys(uid, query)
28 .await
29 .map_err(ApiError::from)?;
2730 Ok(AppResponseData::ok(keys))
28}
No newline at end of file
31}
Modified lib/auth/src/handlers/list_agreements.rs View lib/auth/src/handlers/list_agreements.rs
@@ -2,8 +2,8 @@
22use export::rest::AppResponseData;
33
44use crate::errors::ApiError;
5use crate::services::AuthService;
56use crate::share::types::AgreementDoc;
6use crate::services::AuthService;
77
88#[utoipa::path(
99 get,
@@ -17,4 +17,4 @@
1717 State(service): State<AuthService>,
1818) -> Result<AppResponseData<Vec<AgreementDoc>>, ApiError> {
1919 Ok(AppResponseData::ok(service.list_agreements()))
20}
No newline at end of file
20}
Modified lib/auth/src/handlers/list_notifications.rs View lib/auth/src/handlers/list_notifications.rs
@@ -5,9 +5,9 @@
55use utoipa::IntoParams;
66
77use crate::errors::ApiError;
8use session::share::session::require_uid;
8use crate::services::AuthService;
99use crate::services::notifications::NotificationDto;
10use crate::services::AuthService;
10use session::share::session::require_uid;
1111
1212#[derive(Debug, Clone, Copy, Default, Deserialize, IntoParams)]
1313pub struct ListNotificationsQuery {
@@ -35,4 +35,4 @@
3535 .await
3636 .map_err(ApiError::from)?;
3737 Ok(AppResponseData::ok(notifications))
38}
No newline at end of file
38}
Modified lib/auth/src/handlers/list_ssh_keys.rs View lib/auth/src/handlers/list_ssh_keys.rs
@@ -3,10 +3,10 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
7use crate::share::types::ListQuery;
6use crate::services::AuthService;
87use crate::services::ssh_keys::SshKeyDto;
9use crate::services::AuthService;
8use crate::share::types::ListQuery;
9use session::share::session::require_uid;
1010
1111#[utoipa::path(
1212 get,
@@ -23,6 +23,9 @@
2323 Query(query): Query<ListQuery>,
2424) -> Result<AppResponseData<Vec<SshKeyDto>>, ApiError> {
2525 let uid = require_uid(&session)?;
26 let keys = service.list_ssh_keys(uid, query).await.map_err(ApiError::from)?;
26 let keys = service
27 .list_ssh_keys(uid, query)
28 .await
29 .map_err(ApiError::from)?;
2730 Ok(AppResponseData::ok(keys))
28}
No newline at end of file
31}
Modified lib/auth/src/handlers/login.rs View lib/auth/src/handlers/login.rs
@@ -3,8 +3,8 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::AuthService;
67use crate::services::login::{LoginCmd, LoginResp};
7use crate::services::AuthService;
88
99#[utoipa::path(
1010 post,
@@ -25,4 +25,4 @@
2525 .await
2626 .map_err(ApiError::from)?;
2727 Ok(AppResponseData::ok(resp))
28}
No newline at end of file
28}
Modified lib/auth/src/handlers/login_2fa.rs View lib/auth/src/handlers/login_2fa.rs
@@ -3,8 +3,8 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::AuthService;
67use crate::services::login::{LoginResp, TwoFactorLoginCmd};
7use crate::services::AuthService;
88
99#[utoipa::path(
1010 post,
@@ -25,4 +25,4 @@
2525 .await
2626 .map_err(ApiError::from)?;
2727 Ok(AppResponseData::ok(resp))
28}
No newline at end of file
28}
Modified lib/auth/src/handlers/logout.rs View lib/auth/src/handlers/logout.rs
@@ -3,8 +3,8 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::AuthService;
67use crate::services::logout::LogoutResp;
7use crate::services::AuthService;
88
99#[utoipa::path(
1010 post,
@@ -18,9 +18,6 @@
1818 State(service): State<AuthService>,
1919 session: Session,
2020) -> Result<AppResponseData<LogoutResp>, ApiError> {
21 let resp = service
22 .logout(&session)
23 .await
24 .map_err(ApiError::from)?;
21 let resp = service.logout(&session).await.map_err(ApiError::from)?;
2522 Ok(AppResponseData::ok(resp))
26}
No newline at end of file
23}
Modified lib/auth/src/handlers/mark_all_notifications_read.rs View lib/auth/src/handlers/mark_all_notifications_read.rs
@@ -3,8 +3,8 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::AuthService;
67use session::share::session::require_uid;
7use crate::services::AuthService;
88
99#[utoipa::path(
1010 put,
@@ -24,4 +24,4 @@
2424 .await
2525 .map_err(ApiError::from)?;
2626 Ok(AppResponseData::empty())
27}
No newline at end of file
27}
Modified lib/auth/src/handlers/mark_notification_read.rs View lib/auth/src/handlers/mark_notification_read.rs
@@ -4,8 +4,8 @@
44use uuid::Uuid;
55
66use crate::errors::ApiError;
7use crate::services::AuthService;
78use session::share::session::require_uid;
8use crate::services::AuthService;
99
1010#[utoipa::path(
1111 put,
@@ -29,4 +29,4 @@
2929 .await
3030 .map_err(ApiError::from)?;
3131 Ok(AppResponseData::empty())
32}
No newline at end of file
32}
Modified lib/auth/src/handlers/me.rs View lib/auth/src/handlers/me.rs
@@ -5,9 +5,9 @@
55use utoipa::ToSchema;
66
77use crate::errors::ApiError;
8use session::share::session::{require_uid, session_token_key};
8use crate::services::AuthService;
99use crate::services::profile::{ProfileDto, UserMeDto};
10use crate::services::AuthService;
10use session::share::session::{require_uid, session_token_key};
1111
1212#[derive(Debug, Clone, Serialize, ToSchema)]
1313pub struct MeResp {
@@ -33,4 +33,4 @@
3333 }
3434 let (user, profile) = service.me(uid).await.map_err(ApiError::from)?;
3535 Ok(AppResponseData::ok(MeResp { user, profile }))
36}
No newline at end of file
36}
Modified lib/auth/src/handlers/mod.rs View lib/auth/src/handlers/mod.rs
@@ -19,6 +19,7 @@
1919pub mod list_agreements;
2020pub mod list_notifications;
2121pub mod list_ssh_keys;
22pub mod livechat_token;
2223pub mod login;
2324pub mod login_2fa;
2425pub mod logout;
@@ -44,4 +45,4 @@
4445pub mod update_preferences;
4546pub mod update_profile;
4647pub mod verify_email;
47pub mod verify_email_page;
No newline at end of file
48pub mod verify_email_page;
Modified lib/auth/src/handlers/pow_challenge.rs View lib/auth/src/handlers/pow_challenge.rs
@@ -36,4 +36,4 @@
3636 expires_at: challenge.expires_at,
3737 mac: challenge.mac,
3838 }))
39}
No newline at end of file
39}
Modified lib/auth/src/handlers/reclaim.rs View lib/auth/src/handlers/reclaim.rs
@@ -3,8 +3,8 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::AuthService;
67use crate::services::email::{ReclaimCmd, ReclaimResp};
7use crate::services::AuthService;
88
99#[utoipa::path(
1010 post,
@@ -25,4 +25,4 @@
2525 .await
2626 .map_err(ApiError::from)?;
2727 Ok(AppResponseData::ok(resp))
28}
No newline at end of file
28}
Modified lib/auth/src/handlers/recovery_codes.rs View lib/auth/src/handlers/recovery_codes.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::two_factor::RecoveryCodesResp;
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 get,
@@ -22,4 +22,4 @@
2222 let uid = require_uid(&session)?;
2323 let resp = service.recovery_codes(uid).await.map_err(ApiError::from)?;
2424 Ok(AppResponseData::ok(resp))
25}
No newline at end of file
25}
Modified lib/auth/src/handlers/regenerate_recovery_codes.rs View lib/auth/src/handlers/regenerate_recovery_codes.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::two_factor::{RecoveryCodesResp, RegenerateRecoveryCodesCmd};
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 post,
@@ -27,4 +27,4 @@
2727 .await
2828 .map_err(ApiError::from)?;
2929 Ok(AppResponseData::ok(resp))
30}
No newline at end of file
30}
Modified lib/auth/src/handlers/register.rs View lib/auth/src/handlers/register.rs
@@ -3,8 +3,8 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::AuthService;
67use crate::services::register::{RegisterCmd, RegisterResp};
7use crate::services::AuthService;
88
99#[utoipa::path(
1010 post,
@@ -25,4 +25,4 @@
2525 .await
2626 .map_err(ApiError::from)?;
2727 Ok(AppResponseData::ok(resp))
28}
No newline at end of file
28}
Modified lib/auth/src/handlers/request_deletion.rs View lib/auth/src/handlers/request_deletion.rs
@@ -5,9 +5,9 @@
55use utoipa::ToSchema;
66
77use crate::errors::ApiError;
8use session::share::session::require_uid;
8use crate::services::AuthService;
99use crate::services::deletion::DeletionDto;
10use crate::services::AuthService;
10use session::share::session::require_uid;
1111
1212#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1313pub struct RequestDeletionCmd {
@@ -35,4 +35,4 @@
3535 .await
3636 .map_err(ApiError::from)?;
3737 Ok(AppResponseData::ok(resp))
38}
No newline at end of file
38}
Modified lib/auth/src/handlers/request_email_change.rs View lib/auth/src/handlers/request_email_change.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::email::{EmailChangeTicketDto, RequestEmailChangeCmd};
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 post,
@@ -27,4 +27,4 @@
2727 .await
2828 .map_err(ApiError::from)?;
2929 Ok(AppResponseData::ok(resp))
30}
No newline at end of file
30}
Modified lib/auth/src/handlers/resend_code.rs View lib/auth/src/handlers/resend_code.rs
@@ -3,8 +3,8 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::AuthService;
67use crate::services::email::{ResendCodeCmd, ResendCodeResp};
7use crate::services::AuthService;
88
99#[utoipa::path(
1010 post,
@@ -25,4 +25,4 @@
2525 .await
2626 .map_err(ApiError::from)?;
2727 Ok(AppResponseData::ok(resp))
28}
No newline at end of file
28}
Modified lib/auth/src/handlers/reset_password.rs View lib/auth/src/handlers/reset_password.rs
@@ -2,8 +2,8 @@
22use export::rest::{AppRequest, AppRequestData, AppResponseData, NoData};
33
44use crate::errors::ApiError;
5use crate::services::AuthService;
56use crate::services::password::ResetPasswordCmd;
6use crate::services::AuthService;
77
88#[utoipa::path(
99 post,
@@ -23,4 +23,4 @@
2323 .await
2424 .map_err(ApiError::from)?;
2525 Ok(AppResponseData::empty())
26}
No newline at end of file
26}
Modified lib/auth/src/handlers/totp_disable.rs View lib/auth/src/handlers/totp_disable.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::two_factor::TotpCodeCmd;
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 post,
@@ -27,4 +27,4 @@
2727 .await
2828 .map_err(ApiError::from)?;
2929 Ok(AppResponseData::empty())
30}
No newline at end of file
30}
Modified lib/auth/src/handlers/totp_enable.rs View lib/auth/src/handlers/totp_enable.rs
@@ -3,8 +3,8 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::AuthService;
67use crate::services::two_factor::{RecoveryCodesResp, TotpEnableCmd};
7use crate::services::AuthService;
88use session::share::session::require_uid;
99
1010#[utoipa::path(
@@ -27,4 +27,4 @@
2727 .await
2828 .map_err(ApiError::from)?;
2929 Ok(AppResponseData::ok(resp))
30}
No newline at end of file
30}
Modified lib/auth/src/handlers/totp_setup.rs View lib/auth/src/handlers/totp_setup.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::two_factor::TotpSetupResp;
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 post,
@@ -22,4 +22,4 @@
2222 let uid = require_uid(&session)?;
2323 let resp = service.totp_setup(uid).await.map_err(ApiError::from)?;
2424 Ok(AppResponseData::ok(resp))
25}
No newline at end of file
25}
Modified lib/auth/src/handlers/totp_status.rs View lib/auth/src/handlers/totp_status.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::two_factor::TotpStatusResp;
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 get,
@@ -22,4 +22,4 @@
2222 let uid = require_uid(&session)?;
2323 let resp = service.totp_status(uid).await.map_err(ApiError::from)?;
2424 Ok(AppResponseData::ok(resp))
25}
No newline at end of file
25}
Modified lib/auth/src/handlers/unread_notifications_count.rs View lib/auth/src/handlers/unread_notifications_count.rs
@@ -5,8 +5,8 @@
55use utoipa::ToSchema;
66
77use crate::errors::ApiError;
8use crate::services::AuthService;
89use session::share::session::require_uid;
9use crate::services::AuthService;
1010
1111#[derive(Debug, Clone, Serialize, ToSchema)]
1212pub struct UnreadCountResp {
@@ -31,4 +31,4 @@
3131 .await
3232 .map_err(ApiError::from)?;
3333 Ok(AppResponseData::ok(UnreadCountResp { count }))
34}
No newline at end of file
34}
Modified lib/auth/src/handlers/update_birthday.rs View lib/auth/src/handlers/update_birthday.rs
@@ -6,8 +6,8 @@
66use utoipa::ToSchema;
77
88use crate::errors::ApiError;
9use crate::services::AuthService;
910use session::share::session::require_uid;
10use crate::services::AuthService;
1111
1212#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
1313pub struct UpdateBirthdayCmd {
@@ -34,4 +34,4 @@
3434 .await
3535 .map_err(ApiError::from)?;
3636 Ok(AppResponseData::empty())
37}
No newline at end of file
37}
Modified lib/auth/src/handlers/update_preferences.rs View lib/auth/src/handlers/update_preferences.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::preferences::UpdatePreferencesCmd;
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 patch,
@@ -27,4 +27,4 @@
2727 .await
2828 .map_err(ApiError::from)?;
2929 Ok(AppResponseData::empty())
30}
No newline at end of file
30}
Modified lib/auth/src/handlers/update_profile.rs View lib/auth/src/handlers/update_profile.rs
@@ -3,9 +3,9 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use session::share::session::require_uid;
6use crate::services::AuthService;
77use crate::services::profile::UpdateProfileCmd;
8use crate::services::AuthService;
8use session::share::session::require_uid;
99
1010#[utoipa::path(
1111 patch,
@@ -27,4 +27,4 @@
2727 .await
2828 .map_err(ApiError::from)?;
2929 Ok(AppResponseData::empty())
30}
No newline at end of file
30}
Modified lib/auth/src/handlers/verify_email.rs View lib/auth/src/handlers/verify_email.rs
@@ -2,8 +2,8 @@
22use export::rest::{AppRequest, AppRequestData, AppResponseData};
33
44use crate::errors::ApiError;
5use crate::services::AuthService;
56use crate::services::email::{VerifyEmailCmd, VerifyEmailResp};
6use crate::services::AuthService;
77
88#[utoipa::path(
99 post,
@@ -23,4 +23,4 @@
2323 .await
2424 .map_err(ApiError::from)?;
2525 Ok(AppResponseData::ok(resp))
26}
No newline at end of file
26}
Modified lib/auth/src/handlers/verify_email_page.rs View lib/auth/src/handlers/verify_email_page.rs
@@ -5,8 +5,8 @@
55use uuid::Uuid;
66
77use crate::errors::ApiError;
8use crate::services::AuthService;
89use crate::services::email::VerifyEmailCmd;
9use crate::services::AuthService;
1010use crate::templates;
1111
1212#[derive(Debug, Clone, Deserialize)]
Modified lib/auth/src/pow/mac.rs View lib/auth/src/pow/mac.rs
@@ -1,5 +1,5 @@
1use base64::Engine;
12use base64::engine::general_purpose::STANDARD;
2use base64::Engine;
33use hkdf::Hkdf;
44use hmac::{Hmac, KeyInit, Mac};
55use sha2::Sha256;
@@ -24,7 +24,13 @@
2424 Some(STANDARD.encode(mac.finalize().into_bytes()))
2525}
2626
27pub fn check_mac(master: &[u8], mac_b64: &str, nonce: &str, difficulty: u32, expires_at: i64) -> bool {
27pub fn check_mac(
28 master: &[u8],
29 mac_b64: &str,
30 nonce: &str,
31 difficulty: u32,
32 expires_at: i64,
33) -> bool {
2834 let Ok(expected) = STANDARD.decode(mac_b64) else {
2935 return false;
3036 };
@@ -81,4 +87,4 @@
8187 assert!(!check_mac(KEY, "not-base64!", "nonce-1", 16, 1_000_000));
8288 assert!(!check_mac(KEY, "", "nonce-1", 16, 1_000_000));
8389 }
84}
No newline at end of file
90}
Modified lib/auth/src/pow/mod.rs View lib/auth/src/pow/mod.rs
@@ -2,4 +2,4 @@
22pub mod store;
33
44pub use pow_solver::Challenge;
5pub use store::{issue, verify, PowError};
No newline at end of file
5pub use store::{PowError, issue, verify};
Modified lib/auth/src/pow/store.rs View lib/auth/src/pow/store.rs
@@ -2,7 +2,7 @@
22use std::fmt;
33
44use config::pow::PowConfig;
5use pow_solver::{check, Challenge};
5use pow_solver::{Challenge, check};
66use session::Session;
77use uuid::Uuid;
88
@@ -48,8 +48,13 @@
4848 mac: None,
4949 };
5050 challenge.mac = Some(
51 sign_mac(key.as_bytes(), &challenge.nonce, challenge.difficulty, challenge.expires_at)
52 .ok_or(PowError::InvalidKey)?,
51 sign_mac(
52 key.as_bytes(),
53 &challenge.nonce,
54 challenge.difficulty,
55 challenge.expires_at,
56 )
57 .ok_or(PowError::InvalidKey)?,
5358 );
5459 session.set(CHALLENGE_KEY, challenge.clone());
5560 Ok(challenge)
@@ -63,7 +68,15 @@
6368 let verified = challenge
6469 .mac
6570 .as_deref()
66 .map(|mac| check_mac(key.as_bytes(), mac, &challenge.nonce, challenge.difficulty, challenge.expires_at))
71 .map(|mac| {
72 check_mac(
73 key.as_bytes(),
74 mac,
75 &challenge.nonce,
76 challenge.difficulty,
77 challenge.expires_at,
78 )
79 })
6780 .unwrap_or(false);
6881 if !verified {
6982 return Err(PowError::InvalidSignature);
@@ -78,4 +91,4 @@
7891 }
7992 session.remove(CHALLENGE_KEY);
8093 Ok(())
81}
No newline at end of file
94}
Modified lib/auth/src/services/access_keys.rs View lib/auth/src/services/access_keys.rs
@@ -8,8 +8,8 @@
88use uuid::Uuid;
99
1010use super::AuthService;
11use crate::errors::AuthError;
1112use crate::share::crypto::{constant_time_eq, random_token, sha256_hex};
12use crate::errors::AuthError;
1313use crate::share::types::ListQuery;
1414
1515const ACCESS_TOKEN_BYTES: usize = 32;
@@ -130,7 +130,10 @@
130130 return Ok(None);
131131 };
132132 let db = self.db();
133 let Some(row) = user_access_keys::Entity::find_by_id(key_id).one(db.read()).await? else {
133 let Some(row) = user_access_keys::Entity::find_by_id(key_id)
134 .one(db.read())
135 .await?
136 else {
134137 return Ok(None);
135138 };
136139 let Some(user) = user::Entity::find_by_id(row.user_id).one(db.read()).await? else {
@@ -148,4 +151,4 @@
148151 let scope = row.scope;
149152 Ok(Some(VerifiedAccessKey { user_id, scope }))
150153 }
151}
No newline at end of file
154}
Modified lib/auth/src/services/agreements.rs View lib/auth/src/services/agreements.rs
@@ -34,7 +34,13 @@
3434 key: &str,
3535 version: &str,
3636 ) -> Result<AcceptAgreementResp, AuthError> {
37 let Some(doc) = self.config().auth.agreements.iter().find(|doc| doc.key == key) else {
37 let Some(doc) = self
38 .config()
39 .auth
40 .agreements
41 .iter()
42 .find(|doc| doc.key == key)
43 else {
3844 return Err(AuthError::NotFound);
3945 };
4046 if doc.version != version {
@@ -62,4 +68,4 @@
6268 row.insert(db.write()).await?;
6369 Ok(AcceptAgreementResp { recorded: true })
6470 }
65}
No newline at end of file
71}
Modified lib/auth/src/services/deletion.rs View lib/auth/src/services/deletion.rs
@@ -1,8 +1,6 @@
1use models::users::user_deletion_requests::UserDeletionRequestStatus;
21use models::users::user_deletion_requests;
3use sea_orm::{
4 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set,
5};
2use models::users::user_deletion_requests::UserDeletionRequestStatus;
3use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
64use serde::{Deserialize, Serialize};
75use time::OffsetDateTime;
86use uuid::Uuid;
@@ -37,13 +35,10 @@
3735 let db = self.db();
3836 let active = user_deletion_requests::Entity::find()
3937 .filter(user_deletion_requests::Column::UserId.eq(uid))
40 .filter(
41 user_deletion_requests::Column::Status
42 .is_in([
43 UserDeletionRequestStatus::Pending,
44 UserDeletionRequestStatus::Scheduled,
45 ]),
46 )
38 .filter(user_deletion_requests::Column::Status.is_in([
39 UserDeletionRequestStatus::Pending,
40 UserDeletionRequestStatus::Scheduled,
41 ]))
4742 .one(db.write())
4843 .await?;
4944 if active.is_some() {
@@ -115,4 +110,4 @@
115110 created_at: row.created_at,
116111 }))
117112 }
118}
No newline at end of file
113}
Modified lib/auth/src/services/email.rs View lib/auth/src/services/email.rs
@@ -1,17 +1,17 @@
11use models::users::user;
22use models::users::user_email_change_tickets::EmailChangeTicketStatus;
33use models::users::user_email_verification_codes::EmailVerificationPurpose;
4use models::users::{user_emails, user_email_change_tickets, user_email_verification_codes};
4use models::users::{user_email_change_tickets, user_email_verification_codes, user_emails};
55use sea_orm::sea_query::Expr;
66use sea_orm::{ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, QueryFilter, Set};
7use serde::{Deserialize, Serialize};
78use session::Session;
8use serde::{Deserialize, Serialize};
99use time::OffsetDateTime;
1010use uuid::Uuid;
1111
1212use super::AuthService;
13use crate::share::crypto::{constant_time_eq, generate_numeric_code, sha256_b64url};
1413use crate::errors::AuthError;
14use crate::share::crypto::{constant_time_eq, generate_numeric_code, sha256_b64url};
1515use crate::share::rate::{attempts_reset, attempts_track, rate_limit_once};
1616use crate::share::types::TempKey;
1717use crate::share::validate::validate_email;
@@ -33,10 +33,7 @@
3333) -> TempKey {
3434 let part = purpose.map(purpose_str).unwrap_or_default();
3535 TempKey {
36 name: format!(
37 "auth_{prefix}{part}_{}",
38 email.replace('@', "_at_")
39 ),
36 name: format!("auth_{prefix}{part}_{}", email.replace('@', "_at_")),
4037 value: value.to_string(),
4138 ttl_seconds,
4239 secure: true,
@@ -156,7 +153,13 @@
156153 };
157154 row.insert(db.write()).await?;
158155
159 let temp_key = temp_key("", Some(purpose), &lower_email, temp_id, config.verify_code_ttl_secs);
156 let temp_key = temp_key(
157 "",
158 Some(purpose),
159 &lower_email,
160 temp_id,
161 config.verify_code_ttl_secs,
162 );
160163 Ok(IssueCodeArtifacts { temp_key, code })
161164 }
162165
@@ -186,7 +189,9 @@
186189 }
187190 let provided = cmd.code.trim().to_string();
188191 if !constant_time_eq(stored.as_bytes(), provided.as_bytes()) {
189 return Err(AuthError::BadRequest("invalid verification code".to_string()));
192 return Err(AuthError::BadRequest(
193 "invalid verification code".to_string(),
194 ));
190195 }
191196 crate::share::redis::delete(self.redis(), &code_key).await?;
192197 attempts_reset(self.redis(), &attempts_key).await?;
@@ -204,7 +209,9 @@
204209 .one(db.write())
205210 .await?
206211 else {
207 return Err(AuthError::BadRequest("invalid verification code".to_string()));
212 return Err(AuthError::BadRequest(
213 "invalid verification code".to_string(),
214 ));
208215 };
209216 if row.expires_at <= OffsetDateTime::now_utc() {
210217 return Err(AuthError::BadRequest(
@@ -293,13 +300,9 @@
293300 &self.config().web.web_base_url,
294301 &lower_email,
295302 &artifacts.temp_key.value,
296 );
297 let html = crate::templates::mail_verify_code(
298 &artifacts.code,
299 minutes,
300 subject,
301 link.as_deref(),
302303 );
304 let html =
305 crate::templates::mail_verify_code(&artifacts.code, minutes, subject, link.as_deref());
303306 let body = format!("Your verification code is: {}", artifacts.code);
304307 let sent = self
305308 .send_email(email_row.email.as_str(), subject, &body, html.0.as_str())
@@ -392,9 +395,10 @@
392395 let Some(user) = user::Entity::find_by_id(uid).one(db.read()).await? else {
393396 return Err(AuthError::NotFound);
394397 };
395 let stored = user.passwd_hash.as_deref().ok_or_else(|| {
396 AuthError::BadRequest("account has no password".to_string())
397 })?;
398 let stored = user
399 .passwd_hash
400 .as_deref()
401 .ok_or_else(|| AuthError::BadRequest("account has no password".to_string()))?;
398402 if !crate::share::crypto::verify_password(&cmd.password, stored) {
399403 return Err(AuthError::InvalidCredentials);
400404 }
@@ -515,7 +519,9 @@
515519 }
516520 let code_hash = sha256_b64url(code.trim().as_bytes());
517521 if !constant_time_eq(code_hash.as_bytes(), ticket.token_hash.as_bytes()) {
518 return Err(AuthError::BadRequest("invalid email change code".to_string()));
522 return Err(AuthError::BadRequest(
523 "invalid email change code".to_string(),
524 ));
519525 }
520526 attempts_reset(self.redis(), &attempts_key).await?;
521527
@@ -622,4 +628,4 @@
622628 }
623629 }
624630 out
625}
No newline at end of file
631}
Modified lib/auth/src/services/login.rs View lib/auth/src/services/login.rs
@@ -1,17 +1,15 @@
11use models::users::user;
22use models::users::user_recovery_codes;
33use models::users::{user_devices, user_emails};
4use sea_orm::{
5 ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, Set,
6};
4use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, Set};
5use serde::{Deserialize, Serialize};
76use session::{AuthUser, Session};
8use serde::{Deserialize, Serialize};
97use time::OffsetDateTime;
108use uuid::Uuid;
119
1210use super::AuthService;
11use crate::errors::AuthError;
1312use crate::share::crypto::{aes_decrypt, now_secs, random_token, sha256_b64url};
14use crate::errors::AuthError;
1513use crate::share::rate::{attempts_reset, attempts_track};
1614use crate::share::totp::verify_totp_code;
1715use crate::share::types::UserPublic;
@@ -35,6 +33,8 @@
3533 pub user: UserPublic,
3634 pub needs_2fa: bool,
3735 pub challenge: Option<String>,
36 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub livechat_token: Option<String>,
3838}
3939
4040#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
@@ -62,11 +62,7 @@
6262}
6363
6464impl AuthService {
65 pub async fn login(
66 &self,
67 session: &Session,
68 cmd: LoginCmd,
69 ) -> Result<LoginResp, AuthError> {
65 pub async fn login(&self, session: &Session, cmd: LoginCmd) -> Result<LoginResp, AuthError> {
7066 self.verify_pow(session, &cmd.pow_answer)?;
7167
7268 let db = self.db();
@@ -95,9 +91,10 @@
9591 if !user.is_active {
9692 return Err(AuthError::Forbidden);
9793 }
98 let stored = user.passwd_hash.as_deref().ok_or_else(|| {
99 AuthError::BadRequest("account has no password".to_string())
100 })?;
94 let stored = user
95 .passwd_hash
96 .as_deref()
97 .ok_or_else(|| AuthError::BadRequest("account has no password".to_string()))?;
10198 if !crate::share::crypto::verify_password(&cmd.password, stored) {
10299 return Err(AuthError::InvalidCredentials);
103100 }
@@ -116,6 +113,7 @@
116113 user: UserPublic::from(&user),
117114 needs_2fa: true,
118115 challenge: Some(Uuid::now_v7().to_string()),
116 livechat_token: None,
119117 });
120118 }
121119
@@ -147,7 +145,9 @@
147145 }
148146
149147 let db = self.db();
150 let Some(user) = user::Entity::find_by_id(challenge.uid).one(db.read()).await?
148 let Some(user) = user::Entity::find_by_id(challenge.uid)
149 .one(db.read())
150 .await?
151151 else {
152152 session.remove(TWO_FA_KEY);
153153 return Err(AuthError::BadRequest(
@@ -179,9 +179,10 @@
179179 .totp_encryption_key
180180 .as_deref()
181181 .ok_or_else(|| AuthError::Internal("totp encryption key not configured".to_string()))?;
182 let stored = user.totp_encrypted_secret.as_deref().ok_or_else(|| {
183 AuthError::Internal("totp secret missing".to_string())
184 })?;
182 let stored = user
183 .totp_encrypted_secret
184 .as_deref()
185 .ok_or_else(|| AuthError::Internal("totp secret missing".to_string()))?;
185186 let secret = aes_decrypt(secret_key, stored)?;
186187 match verify_totp_code(&secret, code, user.totp_last_counter) {
187188 Ok((_, counter)) => {
@@ -278,10 +279,23 @@
278279 active.updated_at = Set(now);
279280 let _ = active.update(db.write()).await;
280281
282 let livechat_token = match self.issue_livechat_token(user.uid).await {
283 Ok(Some(token)) => {
284 tracing::debug!("issued livechat token for user {}", user.uid);
285 Some(token)
286 }
287 Ok(None) => None,
288 Err(e) => {
289 tracing::warn!(error = %e, "failed to issue livechat token");
290 None
291 }
292 };
293
281294 Ok(LoginResp {
282295 user: UserPublic::from(user),
283296 needs_2fa: false,
284297 challenge: None,
298 livechat_token,
285299 })
286300 }
287301
@@ -342,14 +356,14 @@
342356 {
343357 return Ok(Some(user));
344358 }
345 Ok(user::Entity::find()
346 .filter(user::Column::LowerName.eq(identifier))
347 .one(db)
348 .await?)
349 }
350 }
359 Ok(user::Entity::find()
360 .filter(user::Column::LowerName.eq(identifier))
361 .one(db)
362 .await?)
363 }
364}
351365
352 enum TotpErrorCategory {
366enum TotpErrorCategory {
353367 Invalid,
354368 MalformedSecret,
355369}
@@ -362,4 +376,4 @@
362376 crate::share::totp::TotpError::MalformedSecret => Self::MalformedSecret,
363377 }
364378 }
365}
No newline at end of file
379}
Modified lib/auth/src/services/logout.rs View lib/auth/src/services/logout.rs
@@ -1,5 +1,5 @@
1use serde::{Deserialize, Serialize};
12use session::{Session, SessionUser, SessionUserMut};
2use serde::{Deserialize, Serialize};
33
44use super::AuthService;
55use crate::errors::AuthError;
@@ -23,4 +23,4 @@
2323 session.remove(&session_token_key(uid));
2424 Ok(LogoutResp { logged_out: true })
2525 }
26}
No newline at end of file
26}
Modified lib/auth/src/services/mod.rs View lib/auth/src/services/mod.rs
@@ -6,8 +6,8 @@
66use models::users::user;
77use models::users::user_sessions;
88use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
9use session::SessionError;
910use session::share::guard::{SessionGuard, SessionWithUser};
10use session::SessionError;
1111
1212use crate::errors::AuthError;
1313
@@ -15,6 +15,7 @@
1515pub mod agreements;
1616pub mod deletion;
1717pub mod email;
18pub mod livechat;
1819pub mod login;
1920pub mod logout;
2021pub mod notifications;
@@ -90,4 +91,4 @@
9091 })
9192 }))
9293 }
93}
No newline at end of file
94}
Modified lib/auth/src/services/notifications.rs View lib/auth/src/services/notifications.rs
@@ -1,8 +1,8 @@
1use models::users::user_notifications;
12use models::users::user_notifications::NotificationType;
2use models::users::user_notifications;
33use sea_orm::{
4 ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, PaginatorTrait,
5 QueryFilter, QueryOrder, QuerySelect, Set,
4 ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, PaginatorTrait, QueryFilter,
5 QueryOrder, QuerySelect, Set,
66};
77use serde::{Deserialize, Serialize};
88use serde_json::Value as JsonValue;
@@ -106,4 +106,4 @@
106106 }
107107 Ok(())
108108 }
109}
No newline at end of file
109}
Modified lib/auth/src/services/password.rs View lib/auth/src/services/password.rs
@@ -1,17 +1,15 @@
11use models::users::{user, user_emails};
2use sea_orm::{
3 ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, Set,
4};
2use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, Set};
3use serde::{Deserialize, Serialize};
54use session::Session;
6use serde::{Deserialize, Serialize};
75use time::OffsetDateTime;
86use uuid::Uuid;
97
108use super::AuthService;
9use crate::errors::AuthError;
1110use crate::share::crypto::{
1211 constant_time_eq, hash_password, random_token, sha256_b64url, validate_password,
1312};
14use crate::errors::AuthError;
1513use crate::share::rate::rate_limit_once;
1614use crate::share::types::TempKey;
1715
@@ -98,12 +96,8 @@
9896 .await?;
9997
10098 let base_url = &self.config().web.web_base_url;
101 let html = crate::templates::mail_reset_link(
102 &lower_email,
103 &token,
104 &temp_key.value,
105 base_url,
106 );
99 let html =
100 crate::templates::mail_reset_link(&lower_email, &token, &temp_key.value, base_url);
107101 let body = format!("Use this reset token within the next minutes: {token}");
108102 let sent = self
109103 .send_email(
@@ -121,16 +115,12 @@
121115 Ok(ForgotPasswordResp { sent, temp_key })
122116 }
123117
124 pub async fn reset_password(
125 &self,
126 cmd: ResetPasswordCmd,
127 ) -> Result<(), AuthError> {
118 pub async fn reset_password(&self, cmd: ResetPasswordCmd) -> Result<(), AuthError> {
128119 let config = &self.config().auth;
129120 validate_password(&cmd.new_password, &config.password_policy)?;
130121
131122 let reset_key = format!("auth:reset:{}", cmd.temp_id);
132 let Some(raw) = crate::share::redis::getdel_string(self.redis(), &reset_key).await?
133 else {
123 let Some(raw) = crate::share::redis::getdel_string(self.redis(), &reset_key).await? else {
134124 return Err(AuthError::BadRequest(
135125 "reset token expired or not found".to_string(),
136126 ));
@@ -139,8 +129,10 @@
139129 .map_err(|error| AuthError::Internal(format!("decode reset token: {error}")))?;
140130 let provided = cmd.token.trim();
141131 let lower_email = cmd.email.trim().to_lowercase();
142 let valid = constant_time_eq(stored.token_hash.as_bytes(), sha256_b64url(provided.as_bytes()).as_bytes())
143 && stored.email == lower_email;
132 let valid = constant_time_eq(
133 stored.token_hash.as_bytes(),
134 sha256_b64url(provided.as_bytes()).as_bytes(),
135 ) && stored.email == lower_email;
144136 if !valid {
145137 return Err(AuthError::BadRequest("invalid reset token".to_string()));
146138 }
@@ -180,9 +172,10 @@
180172 let Some(user) = user::Entity::find_by_id(uid).one(db.write()).await? else {
181173 return Err(AuthError::NotFound);
182174 };
183 let stored = user.passwd_hash.as_deref().ok_or_else(|| {
184 AuthError::BadRequest("account has no password".to_string())
185 })?;
175 let stored = user
176 .passwd_hash
177 .as_deref()
178 .ok_or_else(|| AuthError::BadRequest("account has no password".to_string()))?;
186179 if !crate::share::crypto::verify_password(&cmd.old_password, stored) {
187180 return Err(AuthError::InvalidCredentials);
188181 }
@@ -198,4 +191,4 @@
198191 active.update(db.write()).await?;
199192 Ok(())
200193 }
201}
No newline at end of file
194}
Modified lib/auth/src/services/pow.rs View lib/auth/src/services/pow.rs
@@ -13,4 +13,4 @@
1313 crate::pow::verify(session, &self.config().pow, answer)?;
1414 Ok(())
1515 }
16}
No newline at end of file
16}
Modified lib/auth/src/services/preferences.rs View lib/auth/src/services/preferences.rs
@@ -1,8 +1,6 @@
11use models::users::user::Audience;
22use models::users::user_preferences;
3use sea_orm::{
4 ActiveModelTrait, EntityTrait, IntoActiveModel, Set,
5};
3use sea_orm::{ActiveModelTrait, EntityTrait, IntoActiveModel, Set};
64use serde::{Deserialize, Serialize};
75use time::OffsetDateTime;
86use uuid::Uuid;
@@ -57,7 +55,9 @@
5755
5856impl AuthService {
5957 pub async fn preferences(&self, uid: Uuid) -> Result<UserPreferencesDto, AuthError> {
60 let Some(row) = user_preferences::Entity::find_by_id(uid).one(self.db().read()).await?
58 let Some(row) = user_preferences::Entity::find_by_id(uid)
59 .one(self.db().read())
60 .await?
6161 else {
6262 return Ok(UserPreferencesDto::defaults());
6363 };
@@ -82,7 +82,9 @@
8282 ) -> Result<(), AuthError> {
8383 let db = self.db();
8484 let now = OffsetDateTime::now_utc();
85 let existing = user_preferences::Entity::find_by_id(uid).one(db.write()).await?;
85 let existing = user_preferences::Entity::find_by_id(uid)
86 .one(db.write())
87 .await?;
8688 if let Some(row) = existing {
8789 let mut active = row.into_active_model();
8890 if let Some(theme_mode) = cmd.theme_mode {
@@ -131,12 +133,12 @@
131133 email_digest: Set(cmd.email_digest.unwrap_or(defaults.email_digest)),
132134 mute_until: Set(cmd.mute_until.or(defaults.mute_until)),
133135 allow_dms_from: Set(cmd.allow_dms_from.unwrap_or(defaults.allow_dms_from)),
134 allow_mentions_from: Set(
135 cmd.allow_mentions_from.unwrap_or(defaults.allow_mentions_from),
136 ),
137 show_online_status: Set(
138 cmd.show_online_status.unwrap_or(defaults.show_online_status),
139 ),
136 allow_mentions_from: Set(cmd
137 .allow_mentions_from
138 .unwrap_or(defaults.allow_mentions_from)),
139 show_online_status: Set(cmd
140 .show_online_status
141 .unwrap_or(defaults.show_online_status)),
140142 created_at: Set(now),
141143 updated_at: Set(now),
142144 };
@@ -144,4 +146,4 @@
144146 }
145147 Ok(())
146148 }
147}
No newline at end of file
149}
Modified lib/auth/src/services/profile.rs View lib/auth/src/services/profile.rs
@@ -67,7 +67,9 @@
6767 let Some(user) = user::Entity::find_by_id(uid).one(db.read()).await? else {
6868 return Err(AuthError::NotFound);
6969 };
70 let profile = user_profiles::Entity::find_by_id(uid).one(db.read()).await?;
70 let profile = user_profiles::Entity::find_by_id(uid)
71 .one(db.read())
72 .await?;
7173 let me = UserMeDto {
7274 uid: user.uid,
7375 username: user.username,
@@ -101,11 +103,7 @@
101103 Ok((me, profile))
102104 }
103105
104 pub async fn update_profile(
105 &self,
106 uid: Uuid,
107 cmd: UpdateProfileCmd,
108 ) -> Result<(), AuthError> {
106 pub async fn update_profile(&self, uid: Uuid, cmd: UpdateProfileCmd) -> Result<(), AuthError> {
109107 let db = self.db();
110108 let Some(user) = user::Entity::find_by_id(uid).one(db.write()).await? else {
111109 return Err(AuthError::NotFound);
@@ -211,7 +209,9 @@
211209 validate_age(Some(date_of_birth), self.config().auth.min_age)?;
212210 let db = self.db();
213211 let now = OffsetDateTime::now_utc();
214 let existing = user_profiles::Entity::find_by_id(uid).one(db.write()).await?;
212 let existing = user_profiles::Entity::find_by_id(uid)
213 .one(db.write())
214 .await?;
215215 if let Some(row) = existing {
216216 let mut active = row.into_active_model();
217217 active.date_of_birth = Set(Some(date_of_birth));
@@ -249,4 +249,4 @@
249249 Ok(false)
250250 }
251251 }
252}
No newline at end of file
252}
Modified lib/auth/src/services/register.rs View lib/auth/src/services/register.rs
@@ -1,15 +1,15 @@
11use models::users::user::ProfileVisibility;
22use models::users::{user, user_emails, user_profiles};
33use sea_orm::{ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, QueryFilter, Set};
4use serde::{Deserialize, Serialize};
45use session::Session;
5use serde::{Deserialize, Serialize};
66use time::{Date, OffsetDateTime};
77use uuid::Uuid;
88
99use super::AuthService;
1010use super::email::build_verify_link;
11use crate::share::crypto::{hash_password, validate_password};
1211use crate::errors::AuthError;
12use crate::share::crypto::{hash_password, validate_password};
1313use crate::share::types::TempKey;
1414use crate::share::validate::{derive_username, validate_age, validate_email};
1515
@@ -145,7 +145,11 @@
145145 .await?;
146146
147147 let artifacts = self
148 .issue_code(uid, &lower_email, models::users::user_email_verification_codes::EmailVerificationPurpose::Signup)
148 .issue_code(
149 uid,
150 &lower_email,
151 models::users::user_email_verification_codes::EmailVerificationPurpose::Signup,
152 )
149153 .await?;
150154 let minutes = self.config().auth.verify_code_ttl_secs / 60;
151155 let link = build_verify_link(
@@ -167,7 +171,13 @@
167171 tracing::warn!(%error, "failed to send verification email");
168172 }
169173 for key in &cmd.accept_agreements {
170 if let Some(doc) = self.config().auth.agreements.iter().find(|doc| &doc.key == key) {
174 if let Some(doc) = self
175 .config()
176 .auth
177 .agreements
178 .iter()
179 .find(|doc| &doc.key == key)
180 {
171181 let _ = self.accept_agreement(uid, &doc.key, &doc.version).await;
172182 }
173183 }
@@ -179,4 +189,4 @@
179189 temp_key: artifacts.temp_key,
180190 })
181191 }
182}
No newline at end of file
192}
Modified lib/auth/src/services/reports.rs View lib/auth/src/services/reports.rs
@@ -1,5 +1,5 @@
1use models::users::user_reports;
12use models::users::user_reports::ReportStatus;
2use models::users::user_reports;
33use sea_orm::{ActiveModelTrait, EntityTrait, Set};
44use serde::{Deserialize, Serialize};
55use time::OffsetDateTime;
@@ -32,9 +32,7 @@
3232 cmd: CreateReportCmd,
3333 ) -> Result<ReportDto, AuthError> {
3434 if reporter_uid == cmd.reported_uid {
35 return Err(AuthError::BadRequest(
36 "cannot report yourself".to_string(),
37 ));
35 return Err(AuthError::BadRequest("cannot report yourself".to_string()));
3836 }
3937 let reason = cmd.reason.trim().to_string();
4038 if reason.is_empty() || reason.len() > 500 {
@@ -72,4 +70,4 @@
7270 created_at: inserted.created_at,
7371 })
7472 }
75}
No newline at end of file
73}
Modified lib/auth/src/services/sessions.rs View lib/auth/src/services/sessions.rs
@@ -85,4 +85,4 @@
8585 active.update(self.db().write()).await?;
8686 Ok(())
8787 }
88}
No newline at end of file
88}
Modified lib/auth/src/services/ssh_keys.rs View lib/auth/src/services/ssh_keys.rs
@@ -1,5 +1,5 @@
1use models::users::user_ssh_keys;
12use models::users::user_ssh_keys::SshKeyType;
2use models::users::user_ssh_keys;
33use sea_orm::{
44 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set,
55};
@@ -39,11 +39,7 @@
3939}
4040
4141impl AuthService {
42 pub async fn add_ssh_key(
43 &self,
44 uid: Uuid,
45 cmd: AddSshKeyCmd,
46 ) -> Result<SshKeyDto, AuthError> {
42 pub async fn add_ssh_key(&self, uid: Uuid, cmd: AddSshKeyCmd) -> Result<SshKeyDto, AuthError> {
4743 let name = cmd.name.trim().to_string();
4844 if name.is_empty() || name.len() > 64 {
4945 return Err(AuthError::BadRequest(
@@ -129,4 +125,4 @@
129125 }
130126 Ok(())
131127 }
132}
No newline at end of file
128}
Modified lib/auth/src/services/suspensions.rs View lib/auth/src/services/suspensions.rs
@@ -53,4 +53,4 @@
5353 }
5454 Ok(())
5555 }
56}
No newline at end of file
56}
Modified lib/auth/src/services/two_factor.rs View lib/auth/src/services/two_factor.rs
@@ -8,8 +8,8 @@
88use uuid::Uuid;
99
1010use super::AuthService;
11use crate::errors::AuthError;
1112use crate::share::crypto::{aes_decrypt, aes_encrypt, sha256_b64url};
12use crate::errors::AuthError;
1313use crate::share::totp::{generate_secret, otpauth_uri, verify_totp_code};
1414
1515const RECOVERY_CODE_COUNT: usize = 10;
@@ -81,9 +81,7 @@
8181 .auth
8282 .totp_encryption_key
8383 .as_deref()
84 .ok_or_else(|| {
85 AuthError::Internal("totp encryption key not configured".to_string())
86 })?;
84 .ok_or_else(|| AuthError::Internal("totp encryption key not configured".to_string()))?;
8785 let secret = generate_secret();
8886 let encrypted = aes_encrypt(key, &secret)?;
8987 let otpauth = otpauth_uri(&secret, &user.username);
@@ -109,9 +107,10 @@
109107 let Some(user) = user::Entity::find_by_id(uid).one(db.write()).await? else {
110108 return Err(AuthError::NotFound);
111109 };
112 let stored_password = user.passwd_hash.as_deref().ok_or_else(|| {
113 AuthError::BadRequest("account has no password".to_string())
114 })?;
110 let stored_password = user
111 .passwd_hash
112 .as_deref()
113 .ok_or_else(|| AuthError::BadRequest("account has no password".to_string()))?;
115114 if !crate::share::crypto::verify_password(&cmd.password, stored_password) {
116115 return Err(AuthError::InvalidCredentials);
117116 }
@@ -120,12 +119,11 @@
120119 .auth
121120 .totp_encryption_key
122121 .as_deref()
123 .ok_or_else(|| {
124 AuthError::Internal("totp encryption key not configured".to_string())
125 })?;
126 let stored = user.totp_encrypted_secret.as_deref().ok_or_else(|| {
127 AuthError::BadRequest("run totp setup first".to_string())
128 })?;
122 .ok_or_else(|| AuthError::Internal("totp encryption key not configured".to_string()))?;
123 let stored = user
124 .totp_encrypted_secret
125 .as_deref()
126 .ok_or_else(|| AuthError::BadRequest("run totp setup first".to_string()))?;
129127 let secret = aes_decrypt(key, stored)?;
130128 let (_, counter) = verify_totp_code(&secret, &cmd.code, None)?;
131129 let now = OffsetDateTime::now_utc();
@@ -187,9 +185,7 @@
187185 .auth
188186 .totp_encryption_key
189187 .as_deref()
190 .ok_or_else(|| {
191 AuthError::Internal("totp encryption key not configured".to_string())
192 })?;
188 .ok_or_else(|| AuthError::Internal("totp encryption key not configured".to_string()))?;
193189 let stored = user.totp_encrypted_secret.as_deref().ok_or_else(|| {
194190 AuthError::BadRequest("two-factor authentication is not enabled".to_string())
195191 })?;
@@ -255,9 +251,10 @@
255251 let Some(user) = user::Entity::find_by_id(uid).one(db.read()).await? else {
256252 return Err(AuthError::NotFound);
257253 };
258 let stored = user.passwd_hash.as_deref().ok_or_else(|| {
259 AuthError::BadRequest("account has no password".to_string())
260 })?;
254 let stored = user
255 .passwd_hash
256 .as_deref()
257 .ok_or_else(|| AuthError::BadRequest("account has no password".to_string()))?;
261258 if !crate::share::crypto::verify_password(&cmd.password, stored) {
262259 return Err(AuthError::InvalidCredentials);
263260 }
@@ -295,4 +292,4 @@
295292 codes,
296293 })
297294 }
298}
No newline at end of file
295}
Modified lib/auth/src/services/users.rs View lib/auth/src/services/users.rs
@@ -18,4 +18,4 @@
1818 avatar_url: user.avatar_url,
1919 })
2020 }
21}
No newline at end of file
21}
Modified lib/auth/src/share/crypto.rs View lib/auth/src/share/crypto.rs
@@ -1,12 +1,12 @@
11use std::time::{SystemTime, UNIX_EPOCH};
22
3use aes_gcm::Aes256Gcm;
34use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng};
4use aes_gcm::Aes256Gcm;
55use argon2::password_hash::SaltString;
66use argon2::password_hash::rand_core::OsRng as ArgonOsRng;
77use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
8use base64::Engine;
89use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
9use base64::Engine;
1010use config::auth::PasswordPolicy;
1111use rand::Rng;
1212use sha2::{Digest, Sha256};
@@ -52,9 +52,7 @@
5252 "password must contain a digit".to_string(),
5353 ));
5454 }
55 if policy.require_symbol
56 && !password.chars().any(|c| !c.is_ascii_alphanumeric())
57 {
55 if policy.require_symbol && !password.chars().any(|c| !c.is_ascii_alphanumeric()) {
5856 return Err(AuthError::BadRequest(
5957 "password must contain a symbol".to_string(),
6058 ));
@@ -118,16 +116,12 @@
118116}
119117
120118pub fn aes_decrypt(key_material: &str, stored: &str) -> Result<String, AuthError> {
121 let bytes = STANDARD
122 .decode(stored)
123 .map_err(|error| {
124 tracing::error!(%error, "malformed encrypted value");
125 AuthError::Internal("malformed encrypted value".to_string())
126 })?;
119 let bytes = STANDARD.decode(stored).map_err(|error| {
120 tracing::error!(%error, "malformed encrypted value");
121 AuthError::Internal("malformed encrypted value".to_string())
122 })?;
127123 if bytes.len() < 12 {
128 return Err(AuthError::Internal(
129 "malformed encrypted value".to_string(),
130 ));
124 return Err(AuthError::Internal("malformed encrypted value".to_string()));
131125 }
132126 let (nonce_bytes, ciphertext) = bytes.split_at(12);
133127 let Ok(nonce_arr): Result<&[u8; 12], _> = nonce_bytes.try_into() else {
@@ -156,4 +150,4 @@
156150 .duration_since(UNIX_EPOCH)
157151 .map(|duration| duration.as_secs() as i64)
158152 .unwrap_or(0)
159}
No newline at end of file
153}
Modified lib/auth/src/share/mod.rs View lib/auth/src/share/mod.rs
@@ -6,4 +6,4 @@
66pub mod types;
77pub mod validate;
88
9pub use types::{AgreementDoc, TempKey, UserPublic};
No newline at end of file
9pub use types::{AgreementDoc, TempKey, UserPublic};
Modified lib/auth/src/share/rate.rs View lib/auth/src/share/rate.rs
@@ -27,4 +27,4 @@
2727pub async fn attempts_reset(redis: &AppRedis, key: &str) -> Result<(), AuthError> {
2828 super::redis::delete(redis, key).await?;
2929 Ok(())
30}
No newline at end of file
30}
Modified lib/auth/src/share/redis.rs View lib/auth/src/share/redis.rs
@@ -73,11 +73,7 @@
7373end
7474return c"#;
7575
76pub async fn incr_expire(
77 redis: &AppRedis,
78 key: &str,
79 ttl_secs: u64,
80) -> Result<i64, RedisError> {
76pub async fn incr_expire(redis: &AppRedis, key: &str, ttl_secs: u64) -> Result<i64, RedisError> {
8177 let mut conn = redis.connection_clone();
8278 dispatch!(conn, |c| {
8379 redis::cmd("EVAL")
Modified lib/auth/src/share/totp.rs View lib/auth/src/share/totp.rs
@@ -3,7 +3,7 @@
33use hmac::{Hmac, KeyInit, Mac};
44use sha1::Sha1;
55
6use super::crypto::{constant_time_eq, random_bytes, now_secs};
6use super::crypto::{constant_time_eq, now_secs, random_bytes};
77use crate::errors::AuthError;
88
99pub const TOTP_ISSUER: &str = "Untitled";
@@ -118,7 +118,8 @@
118118 if last_counter.is_some_and(|last| candidate as i64 <= last) {
119119 continue;
120120 }
121 let expected = format_code(hotp(&secret_bytes, candidate).ok_or(TotpError::MalformedSecret)?);
121 let expected =
122 format_code(hotp(&secret_bytes, candidate).ok_or(TotpError::MalformedSecret)?);
122123 if constant_time_eq(expected.as_bytes(), code.as_bytes()) {
123124 return Ok((candidate, candidate as i64));
124125 }
@@ -127,10 +128,14 @@
127128}
128129
129130pub fn otpauth_uri(secret: &str, username: &str) -> String {
130 let encoded_user = username.replace(':', "%3A").replace('?', "%3F").replace('&', "%26").replace('=', "%3D");
131 let encoded_user = username
132 .replace(':', "%3A")
133 .replace('?', "%3F")
134 .replace('&', "%26")
135 .replace('=', "%3D");
131136 format!(
132137 "otpauth://totp/{issuer}:{user}?secret={secret}&issuer={issuer}&algorithm=SHA1&digits=6&period=30",
133138 issuer = TOTP_ISSUER,
134139 user = encoded_user
135140 )
136}
No newline at end of file
141}
Modified lib/auth/src/share/types.rs View lib/auth/src/share/types.rs
@@ -42,4 +42,4 @@
4242 pub version: String,
4343 pub title: String,
4444 pub content_md: String,
45}
No newline at end of file
45}
Modified lib/auth/src/share/validate.rs View lib/auth/src/share/validate.rs
@@ -64,4 +64,4 @@
6464 )));
6565 }
6666 Ok(())
67}
No newline at end of file
67}
Modified lib/auth/src/templates/common.rs View lib/auth/src/templates/common.rs
@@ -1,5 +1,5 @@
11use axum::response::Html;
2use maud::{html, Markup, DOCTYPE};
2use maud::{DOCTYPE, Markup, html};
33
44pub(crate) fn page_shell(title: &str, body: Markup) -> Html<String> {
55 let v = html! {
@@ -17,4 +17,4 @@
1717 }
1818 };
1919 Html(v.into_string())
20}
No newline at end of file
20}
Modified lib/auth/src/templates/mod.rs View lib/auth/src/templates/mod.rs
@@ -1,8 +1,8 @@
11mod common;
22mod page;
33
4use axum::response::Html;
45pub(crate) use common::page_shell;
5use axum::response::Html;
66
77pub fn mail_verify_code(
88 code: &str,
@@ -29,4 +29,4 @@
2929 )
3030}
3131
32pub use page::{page_agreement, page_message, page_verify_result, render_markdown};
No newline at end of file
32pub use page::{page_agreement, page_message, page_verify_result, render_markdown};
Modified lib/auth/src/templates/page.rs View lib/auth/src/templates/page.rs
@@ -27,7 +27,11 @@
2727}
2828
2929pub fn page_verify_result(ok: bool, message: &str) -> Html<String> {
30 let title = if ok { "Email verified" } else { "Verification failed" };
30 let title = if ok {
31 "Email verified"
32 } else {
33 "Verification failed"
34 };
3135 let (bg, title_color) = if ok {
3236 ("bg-green-50", "text-green-800")
3337 } else {
@@ -48,7 +52,11 @@
4852
4953pub fn page_message(ok: bool, title: &str, message: &str) -> Html<String> {
5054 let bg = if ok { "bg-green-50" } else { "bg-blue-50" };
51 let title_color = if ok { "text-green-800" } else { "text-gray-900" };
55 let title_color = if ok {
56 "text-green-800"
57 } else {
58 "text-gray-900"
59 };
5260 let box_classes = format!("{bg} rounded-xl py-10 px-8");
5361 let heading_classes = format!("{title_color} text-2xl font-bold");
5462 let body = html! {
@@ -63,7 +71,7 @@
6371}
6472
6573pub fn render_markdown(input: &str) -> String {
66 use pulldown_cmark::{html, Options, Parser};
74 use pulldown_cmark::{Options, Parser, html};
6775
6876 let mut options = Options::empty();
6977 options.insert(Options::ENABLE_TABLES);
@@ -72,4 +80,4 @@
7280 let mut output = String::new();
7381 html::push_html(&mut output, parser);
7482 output
75}
No newline at end of file
83}
Modified lib/auth/src/errors.rs View lib/auth/src/errors.rs
@@ -1,9 +1,9 @@
11use std::fmt;
22
3use axum::Json;
34use axum::extract::rejection::{PathRejection, QueryRejection};
45use axum::http::StatusCode;
56use axum::response::{IntoResponse, Response};
6use axum::Json;
77use export::rest::{AppResponseData, NoData};
88use utoipa::IntoResponses;
99
@@ -81,6 +81,12 @@
8181 msg.contains("unique") || msg.contains("duplicate") || msg.contains("23505")
8282}
8383
84impl From<lc::Error> for AuthError {
85 fn from(error: lc::Error) -> Self {
86 Self::Internal(format!("livechat error: {error}"))
87 }
88}
89
8490impl From<redis::RedisError> for AuthError {
8591 fn from(error: redis::RedisError) -> Self {
8692 Self::Internal(format!("redis error: {error}"))
@@ -141,7 +147,8 @@
141147impl IntoResponse for ApiError {
142148 fn into_response(self) -> Response {
143149 let (code, envelope) = self.decompose();
144 let status = StatusCode::from_u16(code).unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR);
150 let status =
151 StatusCode::from_u16(code).unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR);
145152 (status, Json(envelope)).into_response()
146153 }
147154}
@@ -149,21 +156,15 @@
149156impl From<AuthError> for ApiError {
150157 fn from(error: AuthError) -> Self {
151158 match error {
152 AuthError::BadRequest(msg) => {
153 Self::BadRequest(AppResponseData::err(400, msg))
154 }
155 AuthError::PowFailed => {
156 Self::BadRequest(Self::payload(400, "proof of work failed"))
157 }
159 AuthError::BadRequest(msg) => Self::BadRequest(AppResponseData::err(400, msg)),
160 AuthError::PowFailed => Self::BadRequest(Self::payload(400, "proof of work failed")),
158161 AuthError::InvalidCredentials => {
159162 Self::InvalidCredentials(Self::payload(400, "invalid credentials"))
160163 }
161164 AuthError::Unauthorized => Self::Unauthorized(Self::payload(401, "unauthorized")),
162165 AuthError::Forbidden => Self::Forbidden(Self::payload(403, "forbidden")),
163166 AuthError::NotFound => Self::NotFound(Self::payload(404, "not found")),
164 AuthError::Conflict(msg) => {
165 Self::Conflict(AppResponseData::err(409, msg))
166 }
167 AuthError::Conflict(msg) => Self::Conflict(AppResponseData::err(409, msg)),
167168 AuthError::RateLimited => Self::RateLimited(Self::payload(429, "rate limit exceeded")),
168169 AuthError::Internal(message) => {
169170 tracing::warn!(error = %message, "auth handler failed");
@@ -216,4 +217,4 @@
216217 tracing::error!(%status, %message, "unhandled (status, message) converted to 500");
217218 Self::Internal(Self::payload(500, "internal server error"))
218219 }
219}
No newline at end of file
220}
Modified lib/auth/src/lib.rs View lib/auth/src/lib.rs
@@ -1,8 +1,8 @@
1pub mod services;
1pub mod errors;
22pub mod handlers;
3pub mod openapi;
34pub mod pow;
4pub mod errors;
5pub mod routes;
6pub mod services;
57pub mod share;
68pub mod templates;
7pub mod openapi;
8pub mod routes;
No newline at end of file
Modified lib/auth/src/openapi.rs View lib/auth/src/openapi.rs
@@ -150,4 +150,4 @@
150150 EmailVerificationPurpose,
151151 ))
152152)]
153pub struct AuthApiDoc;
No newline at end of file
153pub struct AuthApiDoc;
Modified lib/auth/src/routes.rs View lib/auth/src/routes.rs
@@ -1,6 +1,6 @@
1use axum::Router;
12use axum::middleware;
23use axum::routing::{delete, get, post, put};
3use axum::Router;
44
55use crate::handlers::accept_agreement::accept_agreement;
66use crate::handlers::active_suspension::active_suspension;
@@ -23,6 +23,7 @@
2323use crate::handlers::list_agreements::list_agreements;
2424use crate::handlers::list_notifications::list_notifications;
2525use crate::handlers::list_ssh_keys::list_ssh_keys;
26use crate::handlers::livechat_token::livechat_token;
2627use crate::handlers::login::login;
2728use crate::handlers::login_2fa::login_2fa;
2829use crate::handlers::logout::logout;
@@ -73,7 +74,10 @@
7374 let authed = Router::new()
7475 .route("/auth/email/change", post(request_email_change))
7576 .route("/auth/email/change/confirm", post(confirm_email_change))
76 .route("/auth/email/change/{ticket_id}", delete(cancel_email_change))
77 .route(
78 "/auth/email/change/{ticket_id}",
79 delete(cancel_email_change),
80 )
7781 .route("/auth/password/change", post(change_password))
7882 .route("/auth/2fa/status", get(totp_status))
7983 .route("/auth/2fa/setup", post(totp_setup))
@@ -102,11 +106,11 @@
102106 "/notifications/unread-count",
103107 get(unread_notifications_count),
104108 )
109 .route("/notifications/read-all", put(mark_all_notifications_read))
105110 .route(
106 "/notifications/read-all",
107 put(mark_all_notifications_read),
111 "/notifications/{notification_id}",
112 delete(delete_notification),
108113 )
109 .route("/notifications/{notification_id}", delete(delete_notification))
110114 .route(
111115 "/notifications/{notification_id}/read",
112116 put(mark_notification_read),
@@ -115,7 +119,8 @@
115119 .route("/deletion", get(deletion_status).post(request_deletion))
116120 .route("/deletion/cancel", post(cancel_deletion))
117121 .route("/suspension", get(active_suspension))
122 .route("/auth/livechat/token", post(livechat_token))
118123 .route_layer(middleware::from_fn_with_state(service, auth_guard));
119124
120125 public.merge(authed)
121}
No newline at end of file
126}
Modified lib/auth/Cargo.toml View lib/auth/Cargo.toml
@@ -22,6 +22,7 @@
2222sha1 = { workspace = true }
2323uuid = { workspace = true }
2424hkdf = { workspace = true }
25lc = { workspace = true }
2526time = { workspace = true }
2627argon2 = { workspace = true }
2728aes-gcm = { workspace = true }
Modified lib/cache/src/tiered.rs View lib/cache/src/tiered.rs
@@ -3,10 +3,12 @@
33
44use config::cache::CacheConfig;
55use config::redis::RedisMode;
6use oxcache::backend::{CacheReader, CacheWriter, MokaMemoryBackend, RedisBackend, RedisMode as OxRedisMode};
6use oxcache::backend::{
7 CacheReader, CacheWriter, MokaMemoryBackend, RedisBackend, RedisMode as OxRedisMode,
8};
79use oxcache::cache::{ChainCache, ChainLink};
8use serde::de::DeserializeOwned;
910use serde::Serialize;
11use serde::de::DeserializeOwned;
1012
1113use crate::error::CacheError;
1214
Modified lib/config/src/apihost.rs View lib/config/src/apihost.rs
@@ -36,4 +36,4 @@
3636 }
3737 Ok(())
3838 }
39}
No newline at end of file
39}
Modified lib/config/src/auth.rs View lib/config/src/auth.rs
@@ -157,10 +157,11 @@
157157 .map(|bytes| bytes.len())
158158 .unwrap_or(0);
159159 if length < 32 {
160 return Err("auth.totp_encryption_key must be base64 of at least 32 bytes"
161 .to_string());
160 return Err(
161 "auth.totp_encryption_key must be base64 of at least 32 bytes".to_string(),
162 );
162163 }
163164 }
164165 Ok(())
165166 }
166}
No newline at end of file
167}
Modified lib/config/src/database.rs View lib/config/src/database.rs
@@ -81,12 +81,7 @@
8181 if self.url.as_deref().is_some_and(|s| !s.is_empty()) {
8282 return Ok(());
8383 }
84 if self
85 .host
86 .as_deref()
87 .filter(|s| !s.is_empty())
88 .is_none()
89 {
84 if self.host.as_deref().filter(|s| !s.is_empty()).is_none() {
9085 return Err("endpoint.host must be set when endpoint.url is not provided".to_string());
9186 }
9287 if matches!(self.port, Some(0)) {
@@ -189,21 +184,16 @@
189184 if self.url.as_deref().is_some_and(|s| !s.is_empty()) {
190185 return Ok(());
191186 }
192 if self
193 .host
194 .as_deref()
195 .filter(|s| !s.is_empty())
196 .is_none()
197 {
198 return Err(
199 "database.host must be set when database.url is not provided".to_string(),
200 );
187 if self.host.as_deref().filter(|s| !s.is_empty()).is_none() {
188 return Err("database.host must be set when database.url is not provided".to_string());
201189 }
202190 if matches!(self.port, Some(0)) {
203191 return Err("database.port must be greater than 0".to_string());
204192 }
205193 for (i, replica) in self.replicas.iter().enumerate() {
206 replica.validate().map_err(|e| format!("replicas[{i}]: {e}"))?;
194 replica
195 .validate()
196 .map_err(|e| format!("replicas[{i}]: {e}"))?;
207197 }
208198 Ok(())
209199 }
Modified lib/config/src/email.rs View lib/config/src/email.rs
@@ -99,4 +99,4 @@
9999 }
100100 Ok(())
101101 }
102}
No newline at end of file
102}
Modified lib/config/src/email_queue.rs View lib/config/src/email_queue.rs
@@ -46,7 +46,11 @@
4646 if self.channel_prefix.as_deref().is_some_and(|s| s.is_empty()) {
4747 return Err("email_queue.channel_prefix must not be empty".to_string());
4848 }
49 if self.subscriber_group_id.as_deref().is_some_and(|s| s.is_empty()) {
49 if self
50 .subscriber_group_id
51 .as_deref()
52 .is_some_and(|s| s.is_empty())
53 {
5054 return Err(
5155 "email_queue.subscriber_group_id must not be empty when enabled".to_string(),
5256 );
@@ -59,4 +63,4 @@
5963 }
6064 Ok(())
6165 }
62}
No newline at end of file
66}
Modified lib/config/src/error.rs View lib/config/src/error.rs
@@ -13,4 +13,4 @@
1313
1414 #[error("config validation failed: {0}")]
1515 Validation(String),
16}
No newline at end of file
16}
Modified lib/config/src/git.rs View lib/config/src/git.rs
@@ -116,4 +116,4 @@
116116 git_command_timeout_secs: 120,
117117 }
118118 }
119}
No newline at end of file
119}
Modified lib/config/src/githost.rs View lib/config/src/githost.rs
@@ -16,7 +16,8 @@
1616 }
1717
1818 pub fn http_bind_addr(&self) -> IpAddr {
19 self.http_bind_addr.unwrap_or_else(|| "0.0.0.0".parse().expect("static ip"))
19 self.http_bind_addr
20 .unwrap_or_else(|| "0.0.0.0".parse().expect("static ip"))
2021 }
2122
2223 pub fn http_port(&self) -> u16 {
@@ -32,4 +33,4 @@
3233 }
3334 Ok(())
3435 }
35}
No newline at end of file
36}
Modified lib/config/src/lib.rs View lib/config/src/lib.rs
@@ -1,19 +1,20 @@
1pub mod apihost;
12pub mod auth;
2pub mod s3;
3pub mod redis;
43pub mod cache;
54pub mod database;
6pub mod session;
7pub mod pow;
8pub mod web;
9pub mod user;
10pub mod space;
11pub mod issues;
12pub mod git;
135pub mod email;
146pub mod email_queue;
15pub mod apihost;
7pub mod git;
168pub mod githost;
9pub mod issues;
10pub mod livechat;
11pub mod pow;
12pub mod redis;
13pub mod s3;
14pub mod session;
15pub mod space;
16pub mod user;
17pub mod web;
1718
1819pub mod error;
1920pub mod loader;
@@ -26,9 +27,10 @@
2627use crate::database::DatabaseConfig;
2728use crate::email::EmailConfig;
2829use crate::email_queue::EmailQueueConfig;
30use crate::git::GitConfig;
2931use crate::githost::GithostConfig;
30use crate::git::GitConfig;
3132use crate::issues::IssueConfig;
33use crate::livechat::LivechatConfig;
3234use crate::pow::PowConfig;
3335use crate::redis::RedisConfig;
3436use crate::s3::S3Config;
@@ -57,6 +59,7 @@
5759 pub email_queue: EmailQueueConfig,
5860 pub apihost: ApihostConfig,
5961 pub githost: GithostConfig,
62 pub livechat: LivechatConfig,
6063}
6164
6265impl AppConfig {
@@ -71,9 +74,12 @@
7174 self.auth.validate().map_err(ConfigError::Validation)?;
7275 self.cache.validate().map_err(ConfigError::Validation)?;
7376 self.email.is_valid().map_err(ConfigError::Validation)?;
74 self.email_queue.is_valid().map_err(ConfigError::Validation)?;
77 self.email_queue
78 .is_valid()
79 .map_err(ConfigError::Validation)?;
7580 self.apihost.is_valid().map_err(ConfigError::Validation)?;
7681 self.githost.is_valid().map_err(ConfigError::Validation)?;
82 self.livechat.is_valid().map_err(ConfigError::Validation)?;
7783 Ok(())
7884 }
79}
No newline at end of file
85}
Modified lib/config/src/loader.rs View lib/config/src/loader.rs
@@ -1,9 +1,12 @@
11use std::path::Path;
22
3use figment::{Figment, providers::{Env, Format, Toml}};
3use figment::{
4 Figment,
5 providers::{Env, Format, Toml},
6};
47
8use crate::AppConfig;
59use crate::error::ConfigError;
6use crate::AppConfig;
710
811pub const ENV_PREFIX: &str = "APP_";
912pub const ENV_SEPARATOR: &str = "__";
@@ -29,9 +32,8 @@
2932
3033pub fn load_from_env() -> Result<AppConfig, ConfigError> {
3134 let _ = dotenvy::dotenv();
32 let figment = Figment::new()
33 .merge(Env::prefixed(ENV_PREFIX).split(ENV_SEPARATOR));
35 let figment = Figment::new().merge(Env::prefixed(ENV_PREFIX).split(ENV_SEPARATOR));
3436 let cfg: AppConfig = figment.extract().map_err(ConfigError::from)?;
3537 cfg.validate()?;
3638 Ok(cfg)
37}
No newline at end of file
39}
Modified lib/config/src/pow.rs View lib/config/src/pow.rs
@@ -70,4 +70,4 @@
7070 }
7171 Ok(())
7272 }
73}
No newline at end of file
73}
Modified lib/config/src/redis.rs View lib/config/src/redis.rs
@@ -20,7 +20,9 @@
2020 Some(Self::Sentinel)
2121 } else if value.eq_ignore_ascii_case("cluster") {
2222 Some(Self::Cluster)
23 } else if value.eq_ignore_ascii_case("valkey_standalone") || value.eq_ignore_ascii_case("valkey") {
23 } else if value.eq_ignore_ascii_case("valkey_standalone")
24 || value.eq_ignore_ascii_case("valkey")
25 {
2426 Some(Self::ValkeyStandalone)
2527 } else {
2628 None
@@ -70,9 +72,7 @@
7072 return Err("redis.pool_size must be at least 1".to_string());
7173 }
7274 if size > 128 {
73 return Err(format!(
74 "redis.pool_size must not exceed 128, got {size}"
75 ));
75 return Err(format!("redis.pool_size must not exceed 128, got {size}"));
7676 }
7777 }
7878
Modified lib/config/src/session.rs View lib/config/src/session.rs
@@ -18,7 +18,6 @@
1818 None,
1919}
2020
21
2221#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2322#[serde(default)]
2423pub struct SessionConfig {
@@ -42,4 +41,4 @@
4241 pub db_update_interval_secs: Option<i64>,
4342 pub purge_database_update_secs: Option<i64>,
4443 pub enable_bloom_filter: Option<bool>,
45}
No newline at end of file
44}
Modified lib/config/src/web.rs View lib/config/src/web.rs
@@ -4,4 +4,4 @@
44#[serde(default)]
55pub struct WebConfig {
66 pub web_base_url: String,
7}
No newline at end of file
7}
Modified lib/email/src/templates/email_change_confirm.rs View lib/email/src/templates/email_change_confirm.rs
@@ -1,5 +1,5 @@
1use maud::Markup;
12use maud::html;
2use maud::Markup;
33
44use super::{append_query, mail_shell, url_join};
55
@@ -45,18 +45,9 @@
4545}
4646
4747pub fn render(vars: &serde_json::Value) -> Markup {
48 let new_email = vars
49 .get("new_email")
50 .and_then(|v| v.as_str())
51 .unwrap_or("");
52 let ticket_id = vars
53 .get("ticket_id")
54 .and_then(|v| v.as_str())
55 .unwrap_or("");
48 let new_email = vars.get("new_email").and_then(|v| v.as_str()).unwrap_or("");
49 let ticket_id = vars.get("ticket_id").and_then(|v| v.as_str()).unwrap_or("");
5650 let code = vars.get("code").and_then(|v| v.as_str()).unwrap_or("");
57 let base_url = vars
58 .get("base_url")
59 .and_then(|v| v.as_str())
60 .unwrap_or("");
51 let base_url = vars.get("base_url").and_then(|v| v.as_str()).unwrap_or("");
6152 mail_email_change_confirm(new_email, ticket_id, code, base_url)
62}
No newline at end of file
53}
Modified lib/email/src/templates/mod.rs View lib/email/src/templates/mod.rs
@@ -1,4 +1,4 @@
1use maud::{html, Markup, DOCTYPE};
1use maud::{DOCTYPE, Markup, html};
22
33pub mod email_change_confirm;
44pub mod reset_link;
@@ -90,4 +90,4 @@
9090 }
9191 }
9292 out
93}
No newline at end of file
93}
Modified lib/email/src/templates/reset_link.rs View lib/email/src/templates/reset_link.rs
@@ -1,5 +1,5 @@
1use maud::Markup;
12use maud::html;
2use maud::Markup;
33
44use super::{append_query, mail_shell, url_join};
55
@@ -37,4 +37,4 @@
3737 let temp_id = vars.get("temp_id").and_then(|v| v.as_str()).unwrap_or("");
3838 let base_url = vars.get("base_url").and_then(|v| v.as_str()).unwrap_or("");
3939 mail_reset_link(email, token, temp_id, base_url)
40}
No newline at end of file
40}
Modified lib/email/src/templates/verify_code.rs View lib/email/src/templates/verify_code.rs
@@ -1,14 +1,9 @@
1use maud::Markup;
12use maud::html;
2use maud::Markup;
33
44use super::{append_query, mail_shell, url_join};
55
6pub fn mail_verify_code(
7 code: &str,
8 minutes: u64,
9 purpose: &str,
10 link: Option<&str>,
11) -> Markup {
6pub fn mail_verify_code(code: &str, minutes: u64, purpose: &str, link: Option<&str>) -> Markup {
127 let body = html! {
138 h1 style="font-size:24px;font-weight:700;margin:0 0 8px;" { (purpose) }
149 p style="color:#374151;margin:0 0 8px;" {
@@ -34,14 +29,8 @@
3429}
3530
3631pub fn render(vars: &serde_json::Value) -> Markup {
37 let code = vars
38 .get("code")
39 .and_then(|v| v.as_str())
40 .unwrap_or("");
41 let minutes = vars
42 .get("minutes")
43 .and_then(|v| v.as_u64())
44 .unwrap_or(0);
32 let code = vars.get("code").and_then(|v| v.as_str()).unwrap_or("");
33 let minutes = vars.get("minutes").and_then(|v| v.as_u64()).unwrap_or(0);
4534 let purpose = vars
4635 .get("purpose")
4736 .and_then(|v| v.as_str())
@@ -58,4 +47,4 @@
5847 &url_join(base_url, "/auth/verify-email"),
5948 &[("email", email), ("temp_id", temp_id)],
6049 ))
61}
No newline at end of file
50}
Modified lib/email/src/error.rs View lib/email/src/error.rs
@@ -104,4 +104,4 @@
104104 fn from(err: std::io::Error) -> Self {
105105 Self::Io(err)
106106 }
107}
No newline at end of file
107}
Modified lib/email/src/events.rs View lib/email/src/events.rs
@@ -122,4 +122,4 @@
122122 .duration_since(UNIX_EPOCH)
123123 .unwrap_or_default();
124124 dur.as_millis() as i64
125}
No newline at end of file
125}
Modified lib/email/src/lib.rs View lib/email/src/lib.rs
@@ -11,12 +11,12 @@
1111
1212pub use error::EmailError;
1313pub use events::{
14 Attachment, BatchRecipient, EmailAddress, EmailFailed, EmailSent, RenderAndSendEmailRequest,
15 SendBatchEmailRequest, SendEmailRequest, CHANNEL_BATCH, CHANNEL_FAILED, CHANNEL_RENDER,
16 CHANNEL_SEND, CHANNEL_SENT,
14 Attachment, BatchRecipient, CHANNEL_BATCH, CHANNEL_FAILED, CHANNEL_RENDER, CHANNEL_SEND,
15 CHANNEL_SENT, EmailAddress, EmailFailed, EmailSent, RenderAndSendEmailRequest,
16 SendBatchEmailRequest, SendEmailRequest,
1717};
1818pub use queue::{EmailQueue, QueueSettings, SubscribeHandle, TypedMessageMeta};
1919pub use service::{EmailService, EmailServiceBuilder};
20pub use subscriber::{start_subscribers, EmailSubscribers};
20pub use subscriber::{EmailSubscribers, start_subscribers};
2121pub use template::{TemplateFn, TemplateRegistry};
22pub use transport::{build_message, EmailTransport, EmailTransportBuilder};
No newline at end of file
22pub use transport::{EmailTransport, EmailTransportBuilder, build_message};
Modified lib/email/src/queue.rs View lib/email/src/queue.rs
@@ -5,8 +5,8 @@
55
66use redis::aio::ConnectionManager;
77use redis::cluster_async::ClusterConnection;
8use serde::Serialize;
89use serde::de::DeserializeOwned;
9use serde::Serialize;
1010use tokio::task::JoinHandle;
1111use tokio_util::sync::CancellationToken;
1212use tracing::{debug, error, warn};
@@ -201,19 +201,21 @@
201201 if let Some(handle) = self.join.take() {
202202 let join = {
203203 let mut guard = handle.lock().await;
204 std::mem::replace(
205 &mut *guard,
206 tokio::spawn(async { Ok(()) }),
207 )
204 std::mem::replace(&mut *guard, tokio::spawn(async { Ok(()) }))
208205 };
209206 match tokio::time::timeout(timeout, join).await {
210207 Ok(Ok(res)) => res,
211208 Ok(Err(join_err)) => {
212209 error!(error = %join_err, "subscriber task join failed");
213 Err(EmailError::Other(format!("subscriber join failed: {join_err}")))
210 Err(EmailError::Other(format!(
211 "subscriber join failed: {join_err}"
212 )))
214213 }
215214 Err(_) => {
216 warn!(timeout_secs = timeout.as_secs(), "subscriber shutdown timed out");
215 warn!(
216 timeout_secs = timeout.as_secs(),
217 "subscriber shutdown timed out"
218 );
217219 Err(EmailError::Cancelled)
218220 }
219221 }
@@ -306,4 +308,4 @@
306308 }
307309 }
308310 }
309}
No newline at end of file
311}
Modified lib/email/src/runtime.rs View lib/email/src/runtime.rs
@@ -66,7 +66,10 @@
6666 debug!("entering wait_for_shutdown");
6767 wait_for_shutdown().await;
6868 let timeout = Duration::from_secs(5);
69 debug!(timeout_secs = timeout.as_secs(), "calling subscribers.shutdown");
69 debug!(
70 timeout_secs = timeout.as_secs(),
71 "calling subscribers.shutdown"
72 );
7073 worker.subscribers.shutdown(timeout).await?;
7174 info!("email worker shut down");
7275 Ok(())
@@ -75,7 +78,7 @@
7578pub async fn wait_for_shutdown() {
7679 #[cfg(unix)]
7780 {
78 use tokio::signal::unix::{signal, SignalKind};
81 use tokio::signal::unix::{SignalKind, signal};
7982 let mut sigterm = match signal(SignalKind::terminate()) {
8083 Ok(s) => s,
8184 Err(err) => {
@@ -98,4 +101,4 @@
98101 let _ = tokio::signal::ctrl_c().await;
99102 info!("received ctrl-c");
100103 }
101}
No newline at end of file
104}
Modified lib/email/src/service.rs View lib/email/src/service.rs
@@ -7,14 +7,15 @@
77
88use crate::error::EmailError;
99use crate::events::{
10 now_ms, EmailAddress, EmailFailed, EmailSent, RenderAndSendEmailRequest, SendBatchEmailRequest,
11 SendEmailRequest, CHANNEL_BATCH, CHANNEL_FAILED, CHANNEL_RENDER, CHANNEL_SEND, CHANNEL_SENT,
10 CHANNEL_BATCH, CHANNEL_FAILED, CHANNEL_RENDER, CHANNEL_SEND, CHANNEL_SENT, EmailAddress,
11 EmailFailed, EmailSent, RenderAndSendEmailRequest, SendBatchEmailRequest, SendEmailRequest,
12 now_ms,
1213};
1314use crate::queue::EmailQueue;
1415use crate::queue::QueueSettings;
1516use crate::template::TemplateRegistry;
1617use crate::templates;
17use crate::transport::{build_message, EmailTransport, EmailTransportBuilder};
18use crate::transport::{EmailTransport, EmailTransportBuilder, build_message};
1819
1920#[derive(Clone)]
2021pub struct EmailService {
@@ -47,20 +48,12 @@
4748 self.inner.queue.as_ref()
4849 }
4950
50 pub async fn render(
51 &self,
52 name: &str,
53 vars: &serde_json::Value,
54 ) -> Result<String, EmailError> {
51 pub async fn render(&self, name: &str, vars: &serde_json::Value) -> Result<String, EmailError> {
5552 self.inner.templates.render(name, vars).await
5653 }
5754
5855 pub async fn send(&self, mut req: SendEmailRequest) -> Result<EmailSent, EmailError> {
59 let transport = self
60 .inner
61 .transport
62 .as_ref()
63 .ok_or(EmailError::Disabled)?;
56 let transport = self.inner.transport.as_ref().ok_or(EmailError::Disabled)?;
6457 let from = self
6558 .inner
6659 .from
@@ -221,7 +214,11 @@
221214 recipients = recipient_count,
222215 "service::render_and_send dispatch"
223216 );
224 let html = self.inner.templates.render(&req.template, &req.vars).await?;
217 let html = self
218 .inner
219 .templates
220 .render(&req.template, &req.vars)
221 .await?;
225222 debug!(
226223 request_id = %req.request_id,
227224 template = %req.template,
@@ -255,10 +252,7 @@
255252 q.publish_to(CHANNEL_BATCH, req).await
256253 }
257254
258 pub async fn publish_render(
259 &self,
260 req: &RenderAndSendEmailRequest,
261 ) -> Result<(), EmailError> {
255 pub async fn publish_render(&self, req: &RenderAndSendEmailRequest) -> Result<(), EmailError> {
262256 let q = self.inner.queue.as_ref().ok_or(EmailError::Disabled)?;
263257 q.publish_to(CHANNEL_RENDER, req).await
264258 }
@@ -367,12 +361,8 @@
367361 let transport = EmailTransportBuilder::new(
368362 smtp_host.clone(),
369363 smtp_port,
370 self.email_config
371 .smtp_username()
372 .map(|s| s.to_string()),
373 self.email_config
374 .smtp_password()
375 .map(|s| s.to_string()),
364 self.email_config.smtp_username().map(|s| s.to_string()),
365 self.email_config.smtp_password().map(|s| s.to_string()),
376366 )
377367 .connection_timeout(std::time::Duration::from_millis(
378368 self.email_config.connection_timeout_ms(),
@@ -433,4 +423,4 @@
433423 }),
434424 }
435425 }
436}
No newline at end of file
426}
Modified lib/email/src/subscriber.rs View lib/email/src/subscriber.rs
@@ -4,8 +4,8 @@
44
55use crate::error::EmailError;
66use crate::events::{
7 now_ms, EmailFailed, RenderAndSendEmailRequest, SendBatchEmailRequest, SendEmailRequest,
8 CHANNEL_BATCH, CHANNEL_RENDER, CHANNEL_SEND,
7 CHANNEL_BATCH, CHANNEL_RENDER, CHANNEL_SEND, EmailFailed, RenderAndSendEmailRequest,
8 SendBatchEmailRequest, SendEmailRequest, now_ms,
99};
1010use crate::queue::{SubscribeHandle, TypedMessageMeta};
1111use crate::service::EmailService;
@@ -52,9 +52,7 @@
5252 }
5353}
5454
55pub async fn start_subscribers(
56 service: EmailService,
57) -> Result<EmailSubscribers, EmailError> {
55pub async fn start_subscribers(service: EmailService) -> Result<EmailSubscribers, EmailError> {
5856 let queue = match service.queue() {
5957 Some(q) if q.enabled() => q,
6058 _ => {
@@ -62,10 +60,7 @@
6260 return Ok(EmailSubscribers::empty());
6361 }
6462 };
65 let group = service
66 .config()
67 .smtp_host()
68 .to_string();
63 let group = service.config().smtp_host().to_string();
6964 info!(
7065 group = %group,
7166 prefix = %queue.config().channel_prefix,
@@ -78,8 +73,7 @@
7873 move |req: SendEmailRequest, _meta: TypedMessageMeta| {
7974 let svc = svc.clone();
8075 async move {
81 let recipient_count =
82 req.to.len() + req.cc.len() + req.bcc.len();
76 let recipient_count = req.to.len() + req.cc.len() + req.bcc.len();
8377 let request_id = req.request_id;
8478 let metadata = req.metadata.clone();
8579 debug!(
@@ -174,8 +168,7 @@
174168 let svc = svc.clone();
175169 async move {
176170 let request_id = req.request_id;
177 let recipient_count =
178 req.to.len() + req.cc.len() + req.bcc.len();
171 let recipient_count = req.to.len() + req.cc.len() + req.bcc.len();
179172 let metadata = req.metadata.clone();
180173 debug!(
181174 request_id = %request_id,
@@ -225,4 +218,4 @@
225218 batch: Some(batch),
226219 render: Some(render),
227220 })
228}
No newline at end of file
221}
Modified lib/email/src/template.rs View lib/email/src/template.rs
@@ -54,4 +54,4 @@
5454 let guard = self.inner.read().await;
5555 guard.keys().cloned().collect()
5656 }
57}
No newline at end of file
57}
Modified lib/email/src/transport.rs View lib/email/src/transport.rs
@@ -1,15 +1,15 @@
11use std::time::{Duration, Instant};
22
33use base64::Engine;
4use lettre::AsyncTransport;
5use lettre::Tokio1Executor;
46use lettre::address::Address;
7use lettre::message::header::ContentType;
58use lettre::message::{
69 Attachment as LettreAttachment, Mailbox, Message, MessageBuilder, MultiPart, SinglePart,
710};
8use lettre::message::header::ContentType;
11use lettre::transport::smtp::AsyncSmtpTransport;
912use lettre::transport::smtp::authentication::{Credentials, Mechanism};
10use lettre::transport::smtp::AsyncSmtpTransport;
11use lettre::AsyncTransport;
12use lettre::Tokio1Executor;
1313use tracing::debug;
1414
1515use crate::error::EmailError;
@@ -135,7 +135,9 @@
135135 ));
136136 }
137137
138 let mut builder = MessageBuilder::new().from(from).subject(req.subject.clone());
138 let mut builder = MessageBuilder::new()
139 .from(from)
140 .subject(req.subject.clone());
139141
140142 for addr in &req.to {
141143 let mb = to_mailbox(addr)?;
@@ -174,8 +176,8 @@
174176 } else {
175177 let mut mixed = MultiPart::mixed().multipart(body_part);
176178 for att in &req.attachments {
177 let bytes = base64::engine::general_purpose::STANDARD
178 .decode(att.data_b64.as_bytes())?;
179 let bytes =
180 base64::engine::general_purpose::STANDARD.decode(att.data_b64.as_bytes())?;
179181 if bytes.len() > max_attachment_bytes {
180182 return Err(EmailError::AttachmentTooLarge {
181183 actual: bytes.len(),
@@ -187,11 +189,12 @@
187189 } else {
188190 att.content_type.clone()
189191 };
190 let content_type: ContentType = ct_str
191 .parse()
192 .map_err(|e: lettre::message::header::ContentTypeErr| {
193 EmailError::Other(format!("invalid content_type {ct_str:?}: {e}"))
194 })?;
192 let content_type: ContentType =
193 ct_str
194 .parse()
195 .map_err(|e: lettre::message::header::ContentTypeErr| {
196 EmailError::Other(format!("invalid content_type {ct_str:?}: {e}"))
197 })?;
195198 if let Some(cid) = &att.content_id {
196199 mixed = mixed.singlepart(
197200 LettreAttachment::new_inline_with_name(cid.clone(), att.filename.clone())
@@ -207,4 +210,4 @@
207210 };
208211
209212 Ok(message)
210}
No newline at end of file
213}
Modified lib/export/src/database.rs View lib/export/src/database.rs
@@ -1,9 +1,9 @@
11use std::sync::atomic::{AtomicUsize, Ordering};
22
3use crate::error::ExportError;
34use config::database::{DatabaseConfig, DatabaseRole as ConfigRole};
4use sea_orm::{Database, DatabaseConnection, DbBackend, DbErr, ExecResult, QueryResult, Statement};
55use sea_orm::prelude::async_trait::async_trait;
6use crate::error::ExportError;
6use sea_orm::{Database, DatabaseConnection, DbBackend, DbErr, ExecResult, QueryResult, Statement};
77
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum DatabaseRole {
@@ -217,8 +217,7 @@
217217 ')' => depth -= 1,
218218 _ if depth == 0 && i + n.len() <= bytes.len() && &bytes[i..i + n.len()] == n => {
219219 let before_ok = i == 0
220 || !(bytes[i - 1] as char).is_ascii_alphanumeric()
221 && bytes[i - 1] != b'_';
220 || !(bytes[i - 1] as char).is_ascii_alphanumeric() && bytes[i - 1] != b'_';
222221 let after_ok = i + n.len() == bytes.len()
223222 || !(bytes[i + n.len()] as char).is_ascii_alphanumeric()
224223 && bytes[i + n.len()] != b'_';
@@ -254,4 +253,4 @@
254253 async fn query_all_raw(&self, stmt: Statement) -> Result<Vec<QueryResult>, DbErr> {
255254 self.route_for(&stmt.sql).query_all_raw(stmt).await
256255 }
257}
No newline at end of file
256}
Modified lib/export/src/error.rs View lib/export/src/error.rs
@@ -63,4 +63,4 @@
6363 fn from(err: email::EmailError) -> Self {
6464 ExportError::Email(err)
6565 }
66}
No newline at end of file
66}
Modified lib/export/src/lib.rs View lib/export/src/lib.rs
@@ -1,13 +1,16 @@
11pub mod database;
22pub mod error;
33pub mod redis;
4pub mod rest;
45pub mod storage;
5pub mod rest;
66
77use std::sync::Arc;
8use std::time::Duration;
9
810use config::AppConfig;
911pub use database::{AppDatabase, DatabaseRole};
1012pub use error::ExportError;
13pub use lc::LivechatClient;
1114pub use redis::{AppRedis, RedisConnection};
1215pub use storage::AppStorage;
1316
@@ -20,12 +23,13 @@
2023 pub storage: Arc<AppStorage>,
2124 pub email: EmailService,
2225 pub config: AppConfig,
26 pub livechat: Option<LivechatClient>,
2327}
2428
2529impl AppState {
2630 pub async fn load() -> Result<Self, ExportError> {
27 let config = config::AppConfig::load()
28 .map_err(|e| ExportError::InvalidConfig(e.to_string()))?;
31 let config =
32 config::AppConfig::load().map_err(|e| ExportError::InvalidConfig(e.to_string()))?;
2933 Self::with_config(config).await
3034 }
3135
@@ -42,12 +46,30 @@
4246 .register_default_templates()
4347 .await;
4448 let email = builder.build().await?;
49
50 let livechat = if config.livechat.enabled() {
51 let timeout = Duration::from_secs(config.livechat.request_timeout_secs());
52 match LivechatClient::connect_with_timeout(config.livechat.endpoint(), timeout).await {
53 Ok(client) => {
54 tracing::info!("connected to livechat at {}", config.livechat.endpoint());
55 Some(client)
56 }
57 Err(e) => {
58 tracing::warn!(error = %e, "failed to connect to livechat, continuing without it");
59 None
60 }
61 }
62 } else {
63 None
64 };
65
4566 Ok(Self {
4667 database,
4768 redis,
4869 storage,
4970 email,
5071 config,
72 livechat,
5173 })
5274 }
53}
No newline at end of file
75}
Modified lib/export/src/redis.rs View lib/export/src/redis.rs
@@ -1,5 +1,5 @@
1use std::sync::Arc;
12use std::sync::atomic::{AtomicUsize, Ordering};
2use std::sync::Arc;
33
44use config::redis::{RedisConfig, RedisMode};
55use redis::aio::{ConnectionManager, MultiplexedConnection};
@@ -48,7 +48,8 @@
4848 .map(String::from)
4949 .collect::<Vec<_>>();
5050
51 let client = redis::cluster::ClusterClient::new(nodes).map_err(ExportError::from)?;
51 let client =
52 redis::cluster::ClusterClient::new(nodes).map_err(ExportError::from)?;
5253 let node_info = connection_info(url)?;
5354
5455 let mut connections = Vec::with_capacity(pool_size);
@@ -67,7 +68,9 @@
6768 let mut connections = Vec::with_capacity(pool_size);
6869 for _ in 0..pool_size {
6970 let client = redis::Client::open(url).map_err(ExportError::from)?;
70 let manager = ConnectionManager::new(client).await.map_err(ExportError::from)?;
71 let manager = ConnectionManager::new(client)
72 .await
73 .map_err(ExportError::from)?;
7174
7275 connections.push(RedisConnection::Standalone(manager));
7376 }
@@ -141,7 +144,11 @@
141144
142145 let addr = match node_info.addr() {
143146 ConnectionAddr::Tcp(_, _) => ConnectionAddr::Tcp(host.clone(), port),
144 ConnectionAddr::TcpTls { insecure, tls_params, .. } => ConnectionAddr::TcpTls {
147 ConnectionAddr::TcpTls {
148 insecure,
149 tls_params,
150 ..
151 } => ConnectionAddr::TcpTls {
145152 host: host.clone(),
146153 port,
147154 insecure: *insecure,
@@ -194,8 +201,7 @@
194201 continue;
195202 };
196203
197 let (Some(Value::BulkString(host)), Some(Value::Int(port))) =
198 (nodes.first(), nodes.get(1))
204 let (Some(Value::BulkString(host)), Some(Value::Int(port))) = (nodes.first(), nodes.get(1))
199205 else {
200206 continue;
201207 };
@@ -215,7 +221,5 @@
215221 .find(|node| !node.is_empty())
216222 .ok_or_else(|| ExportError::InvalidConfig("redis url contains no nodes".into()))?;
217223
218 Ok(redis::Client::open(first)?
219 .get_connection_info()
220 .clone())
221}
No newline at end of file
224 Ok(redis::Client::open(first)?.get_connection_info().clone())
225}
Modified lib/export/src/storage.rs View lib/export/src/storage.rs
@@ -21,4 +21,4 @@
2121 pub fn client(&self) -> &S3Client {
2222 &self.client
2323 }
24}
No newline at end of file
24}
Modified lib/export/Cargo.toml View lib/export/Cargo.toml
@@ -5,6 +5,7 @@
55
66[dependencies]
77config = { workspace = true }
8lc = { workspace = true }
89storage = { workspace = true }
910sea-orm = { workspace = true }
1011redis = { workspace = true }
@@ -12,4 +13,5 @@
1213email = { workspace = true }
1314axum = { workspace = true }
1415serde = { workspace = true, features = ["derive"] }
16tracing = { workspace = true }
1517utoipa = { workspace = true }
No newline at end of file
Modified lib/git/src/graphql/query/repo.rs View lib/git/src/graphql/query/repo.rs
@@ -10,7 +10,7 @@
1010use crate::graphql::types::{
1111 BlameObj, BlobObj, ChangedPathDiffObj, CodeActivityObj, CommitObj, CommitStatusObj,
1212 ComparisonObj, ComparisonStatus, ContributorStatObj, DiffObj, DiffSummaryObj, GitObject,
13 LanguageStatObj, LanguageStatsObj, LicenseObj, LfsPointerObj, LogOptionsInput, MergeStatusObj,
13 LanguageStatObj, LanguageStatsObj, LfsPointerObj, LicenseObj, LogOptionsInput, MergeStatusObj,
1414 ObjectFormatEnum, PackStatsObj, PageInput, PatchObj, RefObj, RepoStatsObj, RepositoryInfoObj,
1515 SubmoduleObj, TagObj, TagShape, TagTypeEnum, TopicObj, TreeObj,
1616};
@@ -64,12 +64,7 @@
6464 let Some(name) = self.inner.ctx.model.license_name.clone() else {
6565 return Ok(None);
6666 };
67 let dto = self
68 .inner
69 .svc
70 .get_license(&name)
71 .await
72 .map_err(map_git)?;
67 let dto = self.inner.svc.get_license(&name).await.map_err(map_git)?;
7368 Ok(Some(LicenseObj::from(models::repo::repo_license::Model {
7469 name: dto.name,
7570 display_name: dto.display_name,
@@ -101,13 +96,7 @@
10196
10297 async fn repo_stats(&self) -> Result<RepoStatsObj, async_graphql::Error> {
10398 let paths = self.inner.ctx.repo_dir();
104 let timeout = self
105 .inner
106 .svc
107 .state
108 .config
109 .git
110 .git_command_timeout_secs;
99 let timeout = self.inner.svc.state.config.git.git_command_timeout_secs;
111100 let out = crate::service::gitcmd::run_git_soft(
112101 "graphql::repo_stats",
113102 &paths,
@@ -188,7 +177,10 @@
188177 .min(i64::MAX as u64) as i64;
189178 let blob = self
190179 .baby()
191 .get_blob(id, gitbaby::blob::types::GetBlobOptions { limit: Some(cap) })
180 .get_blob(
181 id,
182 gitbaby::blob::types::GetBlobOptions { limit: Some(cap) },
183 )
192184 .await
193185 .map_err(map_git_baby)?;
194186 Ok(BlobObj::new(blob.info.oid, &blob.data, blob.info.size))
Modified lib/git/src/graphql/types/license.rs View lib/git/src/graphql/types/license.rs
@@ -25,4 +25,4 @@
2525 updated_at: DateTimeScalar::from(m.updated_at),
2626 }
2727 }
28}
No newline at end of file
28}
Modified lib/git/src/graphql/types/topic.rs View lib/git/src/graphql/types/topic.rs
@@ -38,4 +38,4 @@
3838 created_at: DateTimeScalar::from(m.created_at),
3939 }
4040 }
41}
No newline at end of file
41}
Modified lib/git/src/handler/analysis/code_activity.rs View lib/git/src/handler/analysis/code_activity.rs
@@ -28,10 +28,16 @@
2828 Path(path): Path<crate::handler::RepoPath>,
2929 Query(q): Query<CodeActivityQuery>,
3030) -> Result<AppResponseData<CodeActivityDto>, ApiError> {
31 let _ = resolve_reader(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
31 let _ = resolve_reader(
32 &service,
33 &session,
34 path.space_name.clone(),
35 path.repo_name.clone(),
36 )
37 .await?;
3238 let out = service
3339 .analysis_code_activity(path.space_name, path.repo_name, q.branch)
3440 .await
3541 .map_err(GitApiError::from)?;
3642 Ok(AppResponseData::ok(out))
37}
No newline at end of file
43}
Modified lib/git/src/handler/analysis/contributors.rs View lib/git/src/handler/analysis/contributors.rs
@@ -21,10 +21,16 @@
2121 session: Session,
2222 Path(path): Path<crate::handler::RepoPath>,
2323) -> Result<AppResponseData<Vec<ContributorStatDto>>, ApiError> {
24 let _ = resolve_reader(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
24 let _ = resolve_reader(
25 &service,
26 &session,
27 path.space_name.clone(),
28 path.repo_name.clone(),
29 )
30 .await?;
2531 let out = service
2632 .analysis_contributors(path.space_name, path.repo_name)
2733 .await
2834 .map_err(GitApiError::from)?;
2935 Ok(AppResponseData::ok(out))
30}
No newline at end of file
36}
Modified lib/git/src/handler/analysis/languages.rs View lib/git/src/handler/analysis/languages.rs
@@ -21,10 +21,16 @@
2121 session: Session,
2222 Path(path): Path<crate::handler::RepoPath>,
2323) -> Result<AppResponseData<Vec<LanguageStatDto>>, ApiError> {
24 let (_ctx, _uid) = require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
24 let (_ctx, _uid) = require_writer(
25 &service,
26 &session,
27 path.space_name.clone(),
28 path.repo_name.clone(),
29 )
30 .await?;
2531 let out = service
2632 .analysis_languages(path.space_name, path.repo_name)
2733 .await
2834 .map_err(GitApiError::from)?;
2935 Ok(AppResponseData::ok(out))
30}
No newline at end of file
36}
Modified lib/git/src/handler/analysis/languages_read.rs View lib/git/src/handler/analysis/languages_read.rs
@@ -21,10 +21,16 @@
2121 session: Session,
2222 Path(path): Path<crate::handler::RepoPath>,
2323) -> Result<AppResponseData<Vec<LanguageStatDto>>, ApiError> {
24 let _ = resolve_reader(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
24 let _ = resolve_reader(
25 &service,
26 &session,
27 path.space_name.clone(),
28 path.repo_name.clone(),
29 )
30 .await?;
2531 let out = service
2632 .analysis_languages_read(path.space_name, path.repo_name)
2733 .await
2834 .map_err(GitApiError::from)?;
2935 Ok(AppResponseData::ok(out))
30}
No newline at end of file
36}
Modified lib/git/src/handler/analysis/mod.rs View lib/git/src/handler/analysis/mod.rs
@@ -1,4 +1,4 @@
11pub mod code_activity;
22pub mod contributors;
33pub mod languages;
4pub mod languages_read;
No newline at end of file
4pub mod languages_read;
Modified lib/git/src/handler/archives/create.rs View lib/git/src/handler/archives/create.rs
@@ -36,4 +36,4 @@
3636 .await
3737 .map_err(GitApiError::from)?;
3838 Ok(AppResponseData::ok(out))
39}
No newline at end of file
39}
Modified lib/git/src/handler/archives/create_bundle.rs View lib/git/src/handler/archives/create_bundle.rs
@@ -21,10 +21,16 @@
2121 session: Session,
2222 Path(path): Path<RepoPath>,
2323) -> Result<AppResponseData<ArchiveResultDto>, ApiError> {
24 require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
24 require_writer(
25 &service,
26 &session,
27 path.space_name.clone(),
28 path.repo_name.clone(),
29 )
30 .await?;
2531 let out = service
2632 .archive_create_bundle(path.space_name, path.repo_name)
2733 .await
2834 .map_err(GitApiError::from)?;
2935 Ok(AppResponseData::ok(out))
30}
No newline at end of file
36}
Modified lib/git/src/handler/archives/download.rs View lib/git/src/handler/archives/download.rs
@@ -38,4 +38,4 @@
3838 .await
3939 .map_err(GitApiError::from)?;
4040 Ok(AppResponseData::ok(out))
41}
No newline at end of file
41}
Modified lib/git/src/handler/archives/mod.rs View lib/git/src/handler/archives/mod.rs
@@ -1,3 +1,3 @@
11pub mod create;
22pub mod create_bundle;
3pub mod download;
No newline at end of file
3pub mod download;
Modified lib/git/src/handler/blobs/content.rs View lib/git/src/handler/blobs/content.rs
@@ -28,10 +28,16 @@
2828 session: Session,
2929 Path(path): Path<BlobPath>,
3030) -> Result<AppResponseData<BlobContentDto>, ApiError> {
31 let _ = resolve_reader(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
31 let _ = resolve_reader(
32 &service,
33 &session,
34 path.space_name.clone(),
35 path.repo_name.clone(),
36 )
37 .await?;
3238 let out = service
3339 .blob_content(path.space_name, path.repo_name, path.oid)
3440 .await
3541 .map_err(GitApiError::from)?;
3642 Ok(AppResponseData::ok(out))
37}
No newline at end of file
43}
Modified lib/git/src/handler/blobs/list_lfs_pointers.rs View lib/git/src/handler/blobs/list_lfs_pointers.rs
@@ -38,10 +38,16 @@
3838 Path(path): Path<RepoQuery>,
3939 Query(q): Query<LfsPointerQuery>,
4040) -> Result<AppResponseData<Vec<LfsPointerDto>>, ApiError> {
41 let _ = resolve_reader(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
41 let _ = resolve_reader(
42 &service,
43 &session,
44 path.space_name.clone(),
45 path.repo_name.clone(),
46 )
47 .await?;
4248 let out = service
4349 .list_lfs_pointers(path.space_name, path.repo_name, q.revision, q.limit)
4450 .await
4551 .map_err(GitApiError::from)?;
4652 Ok(AppResponseData::ok(out))
47}
No newline at end of file
53}
Modified lib/git/src/handler/blobs/metadata.rs View lib/git/src/handler/blobs/metadata.rs
@@ -28,10 +28,16 @@
2828 session: Session,
2929 Path(path): Path<BlobPath>,
3030) -> Result<AppResponseData<BlobMetaDto>, ApiError> {
31 let _ = resolve_reader(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
31 let _ = resolve_reader(
32 &service,
33 &session,
34 path.space_name.clone(),
35 path.repo_name.clone(),
36 )
37 .await?;
3238 let out = service
3339 .blob_metadata(path.space_name, path.repo_name, path.oid)
3440 .await
3541 .map_err(GitApiError::from)?;
3642 Ok(AppResponseData::ok(out))
37}
No newline at end of file
43}
Modified lib/git/src/handler/blobs/mod.rs View lib/git/src/handler/blobs/mod.rs
@@ -1,4 +1,4 @@
11pub mod content;
22pub mod list_lfs_pointers;
33pub mod metadata;
4pub mod raw_download;
No newline at end of file
4pub mod raw_download;
Modified lib/git/src/handler/blobs/raw_download.rs View lib/git/src/handler/blobs/raw_download.rs
@@ -28,10 +28,16 @@
2828 session: Session,
2929 Path(path): Path<BlobPath>,
3030) -> Result<AppResponseData<RawDownloadDto>, ApiError> {
31 let _ = resolve_reader(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
31 let _ = resolve_reader(
32 &service,
33 &session,
34 path.space_name.clone(),
35 path.repo_name.clone(),
36 )
37 .await?;
3238 let out = service
3339 .blob_raw_download(path.space_name, path.repo_name, path.oid)
3440 .await
3541 .map_err(GitApiError::from)?;
3642 Ok(AppResponseData::ok(out))
37}
No newline at end of file
43}
Modified lib/git/src/handler/branches/create.rs View lib/git/src/handler/branches/create.rs
@@ -5,8 +5,8 @@
55
66use crate::errors::{ApiError, GitApiError};
77use crate::handler::require_writer;
8use crate::service::GitService;
89use crate::service::branches::{BranchDto, CreateBranchCmd};
9use crate::service::GitService;
1010
1111#[utoipa::path(
1212 post,
@@ -24,10 +24,16 @@
2424 Path(path): Path<crate::handler::RepoPath>,
2525 Json(cmd): Json<CreateBranchCmd>,
2626) -> Result<AppResponseData<BranchDto>, ApiError> {
27 let _ = require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
27 let _ = require_writer(
28 &service,
29 &session,
30 path.space_name.clone(),
31 path.repo_name.clone(),
32 )
33 .await?;
2834 let out = service
2935 .create_branch(path.space_name, path.repo_name, cmd)
3036 .await
3137 .map_err(GitApiError::from)?;
3238 Ok(AppResponseData::ok(out))
33}
No newline at end of file
39}
Modified lib/git/src/handler/branches/delete.rs View lib/git/src/handler/branches/delete.rs
@@ -34,7 +34,13 @@
3434 Path(path): Path<BranchPath>,
3535 Query(query): Query<DeleteBranchQuery>,
3636) -> Result<AppResponseData<NoData>, ApiError> {
37 let _ = require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
37 let _ = require_writer(
38 &service,
39 &session,
40 path.space_name.clone(),
41 path.repo_name.clone(),
42 )
43 .await?;
3844 service
3945 .delete_branch(
4046 path.space_name,
@@ -45,4 +51,4 @@
4551 .await
4652 .map_err(GitApiError::from)?;
4753 Ok(AppResponseData::ok(NoData))
48}
No newline at end of file
54}
Modified lib/git/src/handler/branches/get.rs View lib/git/src/handler/branches/get.rs
@@ -4,8 +4,8 @@
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::resolve_reader;
7use crate::service::GitService;
78use crate::service::branches::BranchDto;
8use crate::service::GitService;
99
1010#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1111pub struct BranchPath {
@@ -28,10 +28,16 @@
2828 session: Session,
2929 Path(path): Path<BranchPath>,
3030) -> Result<AppResponseData<BranchDto>, ApiError> {
31 let _ = resolve_reader(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
31 let _ = resolve_reader(
32 &service,
33 &session,
34 path.space_name.clone(),
35 path.repo_name.clone(),
36 )
37 .await?;
3238 let out = service
3339 .get_branch(path.space_name, path.repo_name, path.name)
3440 .await
3541 .map_err(GitApiError::from)?;
3642 Ok(AppResponseData::ok(out))
37}
No newline at end of file
43}
Modified lib/git/src/handler/blame.rs View lib/git/src/handler/blame.rs
@@ -4,8 +4,8 @@
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::resolve_reader;
7use crate::service::GitService;
78use crate::service::blame::BlameRangeDto;
8use crate::service::GitService;
99
1010#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1111pub struct BlameQuery {
@@ -35,8 +35,13 @@
3535 Path(path): Path<crate::handler::RepoPath>,
3636 Query(q): Query<BlameQuery>,
3737) -> Result<AppResponseData<Vec<BlameRangeDto>>, ApiError> {
38 let _ = resolve_reader(&service, &session, path.space_name.clone(), path.repo_name.clone())
39 .await?;
38 let _ = resolve_reader(
39 &service,
40 &session,
41 path.space_name.clone(),
42 path.repo_name.clone(),
43 )
44 .await?;
4045 let ranges = service
4146 .blame(
4247 path.space_name,
@@ -49,4 +54,4 @@
4954 .await
5055 .map_err(GitApiError::from)?;
5156 Ok(AppResponseData::ok(ranges))
52}
No newline at end of file
57}
Modified lib/git/src/handler/graphql.rs View lib/git/src/handler/graphql.rs
@@ -5,7 +5,7 @@
55use uuid::Uuid;
66
77use crate::errors::ApiError;
8use crate::graphql::{build_schema, GitSchema};
8use crate::graphql::{GitSchema, build_schema};
99use crate::service::GitService;
1010
1111#[utoipa::path(
@@ -48,4 +48,4 @@
4848#[allow(dead_code)]
4949pub fn build_schema_for(service: GitService) -> GitSchema {
5050 build_schema(service.state)
51}
No newline at end of file
51}
Modified lib/git/src/handler/licenses.rs View lib/git/src/handler/licenses.rs
@@ -1,5 +1,5 @@
1use axum::Json;
12use axum::extract::{Path, Query, State};
2use axum::Json;
33use export::rest::AppResponseData;
44use serde::Deserialize;
55use utoipa::IntoParams;
@@ -34,7 +34,10 @@
3434) -> Result<AppResponseData<Vec<LicenseDto>>, ApiError> {
3535 let limit = q.limit.unwrap_or(50).clamp(1, 100);
3636 let offset = q.offset.unwrap_or(0);
37 let out = service.list_licenses(limit, offset).await.map_err(GitApiError::from)?;
37 let out = service
38 .list_licenses(limit, offset)
39 .await
40 .map_err(GitApiError::from)?;
3841 Ok(AppResponseData::ok(out))
3942}
4043
@@ -51,7 +54,10 @@
5154 State(service): State<GitService>,
5255 Path(p): Path<LicenseNamePath>,
5356) -> Result<AppResponseData<LicenseDto>, ApiError> {
54 let out = service.get_license(&p.name).await.map_err(GitApiError::from)?;
57 let out = service
58 .get_license(&p.name)
59 .await
60 .map_err(GitApiError::from)?;
5561 Ok(AppResponseData::ok(out))
5662}
5763
@@ -70,6 +76,9 @@
7076 Path(p): Path<LicenseNamePath>,
7177 Json(cmd): Json<UpsertLicenseCmd>,
7278) -> Result<AppResponseData<LicenseDto>, ApiError> {
73 let out = service.upsert_license(&p.name, cmd).await.map_err(GitApiError::from)?;
79 let out = service
80 .upsert_license(&p.name, cmd)
81 .await
82 .map_err(GitApiError::from)?;
7483 Ok(AppResponseData::ok(out))
75}
No newline at end of file
84}
Modified lib/git/src/handler/mod.rs View lib/git/src/handler/mod.rs
@@ -10,8 +10,8 @@
1010pub mod graphql;
1111pub mod hooks;
1212pub mod html;
13pub mod lfs;
1314pub mod licenses;
14pub mod lfs;
1515pub mod logs;
1616pub mod maintenance;
1717pub mod packs;
@@ -33,4 +33,4 @@
3333
3434pub use share::{
3535 RepoPath, RepoPathRev, ensure_caller, require_repo_admin, require_writer, resolve_reader,
36};
No newline at end of file
36};
Modified lib/git/src/handler/path_log.rs View lib/git/src/handler/path_log.rs
@@ -31,8 +31,13 @@
3131 Path(path): Path<crate::handler::RepoPath>,
3232 Query(q): Query<PathLogQuery>,
3333) -> Result<AppResponseData<Vec<PathLogEntryDto>>, ApiError> {
34 let _ = resolve_reader(&service, &session, path.space_name.clone(), path.repo_name.clone())
35 .await?;
34 let _ = resolve_reader(
35 &service,
36 &session,
37 path.space_name.clone(),
38 path.repo_name.clone(),
39 )
40 .await?;
3641 let entries = service
3742 .path_log(
3843 path.space_name,
@@ -43,4 +48,4 @@
4348 .await
4449 .map_err(GitApiError::from)?;
4550 Ok(AppResponseData::ok(entries))
46}
No newline at end of file
51}
Modified lib/git/src/handler/share.rs View lib/git/src/handler/share.rs
@@ -109,4 +109,4 @@
109109 .await
110110 .map_err(super::super::errors::GitApiError::from)?;
111111 Ok((ctx, uid))
112}
No newline at end of file
112}
Modified lib/git/src/handler/stars.rs View lib/git/src/handler/stars.rs
@@ -40,8 +40,17 @@
4040 session: Session,
4141 Path(path): Path<RepoPath>,
4242) -> Result<AppResponseData<RepoStarResp>, ApiError> {
43 let (ctx, uid) = require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
44 let out = service.star_repo(&ctx, uid).await.map_err(GitApiError::from)?;
43 let (ctx, uid) = require_writer(
44 &service,
45 &session,
46 path.space_name.clone(),
47 path.repo_name.clone(),
48 )
49 .await?;
50 let out = service
51 .star_repo(&ctx, uid)
52 .await
53 .map_err(GitApiError::from)?;
4554 Ok(AppResponseData::ok(out))
4655}
4756
@@ -59,8 +68,17 @@
5968 session: Session,
6069 Path(path): Path<RepoPath>,
6170) -> Result<AppResponseData<RepoStarResp>, ApiError> {
62 let (ctx, uid) = require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
63 let out = service.unstar_repo(&ctx, uid).await.map_err(GitApiError::from)?;
71 let (ctx, uid) = require_writer(
72 &service,
73 &session,
74 path.space_name.clone(),
75 path.repo_name.clone(),
76 )
77 .await?;
78 let out = service
79 .unstar_repo(&ctx, uid)
80 .await
81 .map_err(GitApiError::from)?;
6482 Ok(AppResponseData::ok(out))
6583}
6684
@@ -78,8 +96,17 @@
7896 session: Session,
7997 Path(path): Path<RepoPath>,
8098) -> Result<AppResponseData<StarStatusResp>, ApiError> {
81 let (ctx, uid) = require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
82 let starred = service.is_starred(&ctx, uid).await.map_err(GitApiError::from)?;
99 let (ctx, uid) = require_writer(
100 &service,
101 &session,
102 path.space_name.clone(),
103 path.repo_name.clone(),
104 )
105 .await?;
106 let starred = service
107 .is_starred(&ctx, uid)
108 .await
109 .map_err(GitApiError::from)?;
83110 Ok(AppResponseData::ok(StarStatusResp { starred }))
84111}
85112
@@ -98,12 +125,21 @@
98125 Path(path): Path<RepoPath>,
99126 Query(q): Query<StarQuery>,
100127) -> Result<AppResponseData<Vec<StargazerDto>>, ApiError> {
101 let (ctx, _uid) = require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
128 let (ctx, _uid) = require_writer(
129 &service,
130 &session,
131 path.space_name.clone(),
132 path.repo_name.clone(),
133 )
134 .await?;
102135 let limit = q.limit.unwrap_or(50).clamp(1, 100);
103136 let offset = q.offset.unwrap_or(0);
104 let out = service.list_stargazers(&ctx, limit, offset).await.map_err(GitApiError::from)?;
137 let out = service
138 .list_stargazers(&ctx, limit, offset)
139 .await
140 .map_err(GitApiError::from)?;
105141 Ok(AppResponseData::ok(out))
106142}
107143
108144#[allow(dead_code)]
109fn _typecheck(_: AppResponseData<RepoStarResp>) {}
No newline at end of file
145fn _typecheck(_: AppResponseData<RepoStarResp>) {}
Modified lib/git/src/handler/topics.rs View lib/git/src/handler/topics.rs
@@ -1,5 +1,5 @@
1use axum::Json;
12use axum::extract::{Path, Query, State};
2use axum::Json;
33use export::rest::{AppResponseData, NoData};
44use serde::{Deserialize, Serialize};
55use session::Session;
@@ -46,7 +46,10 @@
4646 State(service): State<GitService>,
4747 Json(cmd): Json<CreateTopicCmd>,
4848) -> Result<AppResponseData<TopicDto>, ApiError> {
49 let out = service.create_topic(cmd.name).await.map_err(GitApiError::from)?;
49 let out = service
50 .create_topic(cmd.name)
51 .await
52 .map_err(GitApiError::from)?;
5053 Ok(AppResponseData::ok(out))
5154}
5255
@@ -65,7 +68,10 @@
6568) -> Result<AppResponseData<Vec<TopicDto>>, ApiError> {
6669 let limit = q.limit.unwrap_or(50).clamp(1, 100);
6770 let offset = q.offset.unwrap_or(0);
68 let out = service.list_topics(limit, offset).await.map_err(GitApiError::from)?;
71 let out = service
72 .list_topics(limit, offset)
73 .await
74 .map_err(GitApiError::from)?;
6975 Ok(AppResponseData::ok(out))
7076}
7177
@@ -82,7 +88,10 @@
8288 State(service): State<GitService>,
8389 Path(p): Path<TopicNamePath>,
8490) -> Result<AppResponseData<TopicDto>, ApiError> {
85 let out = service.get_topic(&p.name).await.map_err(GitApiError::from)?;
91 let out = service
92 .get_topic(&p.name)
93 .await
94 .map_err(GitApiError::from)?;
8695 Ok(AppResponseData::ok(out))
8796}
8897
@@ -102,7 +111,10 @@
102111) -> Result<AppResponseData<Vec<TopicRepoDto>>, ApiError> {
103112 let limit = q.limit.unwrap_or(50).clamp(1, 100);
104113 let offset = q.offset.unwrap_or(0);
105 let out = service.list_repos_by_topic(&p.name, limit, offset).await.map_err(GitApiError::from)?;
114 let out = service
115 .list_repos_by_topic(&p.name, limit, offset)
116 .await
117 .map_err(GitApiError::from)?;
106118 Ok(AppResponseData::ok(out))
107119}
108120
@@ -120,8 +132,17 @@
120132 session: Session,
121133 Path(p): Path<RepoTopicPath>,
122134) -> Result<AppResponseData<TopicDto>, ApiError> {
123 let (ctx, _uid) = require_writer(&service, &session, p.space_name.clone(), p.repo_name.clone()).await?;
124 let out = service.add_topic_to_repo(&ctx, &p.topic_name).await.map_err(GitApiError::from)?;
135 let (ctx, _uid) = require_writer(
136 &service,
137 &session,
138 p.space_name.clone(),
139 p.repo_name.clone(),
140 )
141 .await?;
142 let out = service
143 .add_topic_to_repo(&ctx, &p.topic_name)
144 .await
145 .map_err(GitApiError::from)?;
125146 Ok(AppResponseData::ok(out))
126147}
127148
@@ -139,8 +160,17 @@
139160 session: Session,
140161 Path(p): Path<RepoTopicPath>,
141162) -> Result<AppResponseData<NoData>, ApiError> {
142 let (ctx, _uid) = require_writer(&service, &session, p.space_name.clone(), p.repo_name.clone()).await?;
143 service.remove_topic_from_repo(&ctx, &p.topic_name).await.map_err(GitApiError::from)?;
163 let (ctx, _uid) = require_writer(
164 &service,
165 &session,
166 p.space_name.clone(),
167 p.repo_name.clone(),
168 )
169 .await?;
170 service
171 .remove_topic_from_repo(&ctx, &p.topic_name)
172 .await
173 .map_err(GitApiError::from)?;
144174 Ok(AppResponseData::empty())
145175}
146176
@@ -158,7 +188,16 @@
158188 session: Session,
159189 Path(path): Path<RepoPath>,
160190) -> Result<AppResponseData<Vec<TopicDto>>, ApiError> {
161 let (ctx, _uid) = resolve_reader(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
162 let out = service.list_repo_topics(&ctx).await.map_err(GitApiError::from)?;
191 let (ctx, _uid) = resolve_reader(
192 &service,
193 &session,
194 path.space_name.clone(),
195 path.repo_name.clone(),
196 )
197 .await?;
198 let out = service
199 .list_repo_topics(&ctx)
200 .await
201 .map_err(GitApiError::from)?;
163202 Ok(AppResponseData::ok(out))
164}
No newline at end of file
203}
Modified lib/git/src/handler/watches.rs View lib/git/src/handler/watches.rs
@@ -1,5 +1,5 @@
1use axum::Json;
12use axum::extract::{Path, Query, State};
2use axum::Json;
33use export::rest::{AppRequestData, AppResponseData};
44use models::common::watch_mode::WatchMode;
55use serde::Deserialize;
@@ -34,8 +34,17 @@
3434 Path(path): Path<RepoPath>,
3535 Json(cmd): Json<UpsertWatchCmd>,
3636) -> Result<AppResponseData<RepoWatchDto>, ApiError> {
37 let (ctx, uid) = require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
38 let out = service.upsert_watch(&ctx, uid, cmd).await.map_err(GitApiError::from)?;
37 let (ctx, uid) = require_writer(
38 &service,
39 &session,
40 path.space_name.clone(),
41 path.repo_name.clone(),
42 )
43 .await?;
44 let out = service
45 .upsert_watch(&ctx, uid, cmd)
46 .await
47 .map_err(GitApiError::from)?;
3948 Ok(AppResponseData::ok(out))
4049}
4150
@@ -53,8 +62,17 @@
5362 session: Session,
5463 Path(path): Path<RepoPath>,
5564) -> Result<AppResponseData<RepoWatchDto>, ApiError> {
56 let (ctx, uid) = require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
57 service.unwatch(&ctx, uid).await.map_err(GitApiError::from)?;
65 let (ctx, uid) = require_writer(
66 &service,
67 &session,
68 path.space_name.clone(),
69 path.repo_name.clone(),
70 )
71 .await?;
72 service
73 .unwatch(&ctx, uid)
74 .await
75 .map_err(GitApiError::from)?;
5876 Ok(AppResponseData::ok(RepoWatchDto {
5977 user_id: uid,
6078 repo_id: ctx.model.id,
@@ -78,8 +96,17 @@
7896 session: Session,
7997 Path(path): Path<RepoPath>,
8098) -> Result<AppResponseData<Option<WatchMode>>, ApiError> {
81 let (ctx, uid) = require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
82 let mode = service.get_watch_mode(&ctx, uid).await.map_err(GitApiError::from)?;
99 let (ctx, uid) = require_writer(
100 &service,
101 &session,
102 path.space_name.clone(),
103 path.repo_name.clone(),
104 )
105 .await?;
106 let mode = service
107 .get_watch_mode(&ctx, uid)
108 .await
109 .map_err(GitApiError::from)?;
83110 Ok(AppResponseData::ok(mode))
84111}
85112
@@ -98,12 +125,21 @@
98125 Path(path): Path<RepoPath>,
99126 Query(q): Query<WatchQuery>,
100127) -> Result<AppResponseData<Vec<WatcherDto>>, ApiError> {
101 let (ctx, _uid) = require_writer(&service, &session, path.space_name.clone(), path.repo_name.clone()).await?;
128 let (ctx, _uid) = require_writer(
129 &service,
130 &session,
131 path.space_name.clone(),
132 path.repo_name.clone(),
133 )
134 .await?;
102135 let limit = q.limit.unwrap_or(50).clamp(1, 100);
103136 let offset = q.offset.unwrap_or(0);
104 let out = service.list_watchers(&ctx, q.mode, limit, offset).await.map_err(GitApiError::from)?;
137 let out = service
138 .list_watchers(&ctx, q.mode, limit, offset)
139 .await
140 .map_err(GitApiError::from)?;
105141 Ok(AppResponseData::ok(out))
106142}
107143
108144#[allow(dead_code)]
109fn _typecheck(_: AppRequestData<UpsertWatchCmd>) {}
No newline at end of file
145fn _typecheck(_: AppRequestData<UpsertWatchCmd>) {}
Modified lib/git/src/handler/whitelists.rs View lib/git/src/handler/whitelists.rs
@@ -1,5 +1,5 @@
1use axum::Json;
12use axum::extract::{Path, State};
2use axum::Json;
33use export::rest::{AppRequestData, AppResponseData, NoData};
44use serde::Deserialize;
55use utoipa::IntoParams;
@@ -56,7 +56,10 @@
5656 Path(p): Path<BranchPolicyPath>,
5757 Json(cmd): Json<AddBranchWhitelistCmd>,
5858) -> Result<AppResponseData<BranchWhitelistDto>, ApiError> {
59 let out = service.add_branch_whitelist(p.policy_id, cmd).await.map_err(GitApiError::from)?;
59 let out = service
60 .add_branch_whitelist(p.policy_id, cmd)
61 .await
62 .map_err(GitApiError::from)?;
6063 Ok(AppResponseData::ok(out))
6164}
6265
@@ -73,7 +76,10 @@
7376 State(service): State<GitService>,
7477 Path(p): Path<BranchWhitelistPath>,
7578) -> Result<AppResponseData<NoData>, ApiError> {
76 service.remove_branch_whitelist(p.policy_id, p.whitelist_id).await.map_err(GitApiError::from)?;
79 service
80 .remove_branch_whitelist(p.policy_id, p.whitelist_id)
81 .await
82 .map_err(GitApiError::from)?;
7783 Ok(AppResponseData::empty())
7884}
7985
@@ -90,7 +96,10 @@
9096 State(service): State<GitService>,
9197 Path(p): Path<BranchPolicyPath>,
9298) -> Result<AppResponseData<Vec<BranchWhitelistDto>>, ApiError> {
93 let out = service.list_branch_whitelists(p.policy_id).await.map_err(GitApiError::from)?;
99 let out = service
100 .list_branch_whitelists(p.policy_id)
101 .await
102 .map_err(GitApiError::from)?;
94103 Ok(AppResponseData::ok(out))
95104}
96105
@@ -109,7 +118,10 @@
109118 Path(p): Path<TagPolicyPath>,
110119 Json(cmd): Json<AddTagWhitelistCmd>,
111120) -> Result<AppResponseData<TagWhitelistDto>, ApiError> {
112 let out = service.add_tag_whitelist(p.policy_id, cmd).await.map_err(GitApiError::from)?;
121 let out = service
122 .add_tag_whitelist(p.policy_id, cmd)
123 .await
124 .map_err(GitApiError::from)?;
113125 Ok(AppResponseData::ok(out))
114126}
115127
@@ -126,7 +138,10 @@
126138 State(service): State<GitService>,
127139 Path(p): Path<TagWhitelistPath>,
128140) -> Result<AppResponseData<NoData>, ApiError> {
129 service.remove_tag_whitelist(p.policy_id, p.user_id).await.map_err(GitApiError::from)?;
141 service
142 .remove_tag_whitelist(p.policy_id, p.user_id)
143 .await
144 .map_err(GitApiError::from)?;
130145 Ok(AppResponseData::empty())
131146}
132147
@@ -143,9 +158,12 @@
143158 State(service): State<GitService>,
144159 Path(p): Path<TagPolicyPath>,
145160) -> Result<AppResponseData<Vec<TagWhitelistDto>>, ApiError> {
146 let out = service.list_tag_whitelists(p.policy_id).await.map_err(GitApiError::from)?;
161 let out = service
162 .list_tag_whitelists(p.policy_id)
163 .await
164 .map_err(GitApiError::from)?;
147165 Ok(AppResponseData::ok(out))
148166}
149167
150168#[allow(dead_code)]
151fn _typecheck(_: AppRequestData<AddBranchWhitelistCmd>, _: AppRequestData<AddTagWhitelistCmd>) {}
No newline at end of file
169fn _typecheck(_: AppRequestData<AddBranchWhitelistCmd>, _: AppRequestData<AddTagWhitelistCmd>) {}
Modified lib/git/src/lfs/error.rs View lib/git/src/lfs/error.rs
@@ -1,6 +1,5 @@
11pub use crate::share::errors::GitError;
22
3
43pub fn http_status(err: &GitError) -> u16 {
54 match err {
65 GitError::NotFound => 404,
@@ -11,4 +10,3 @@
1110 GitError::Cache(_) | GitError::Git(_) | GitError::Internal(_) => 500,
1211 }
1312}
14
Modified lib/git/src/lfs/handle.rs View lib/git/src/lfs/handle.rs
@@ -400,4 +400,4 @@
400400 }
401401 }
402402 }
403}
No newline at end of file
403}
Modified lib/git/src/lfs/lock.rs View lib/git/src/lfs/lock.rs
@@ -14,8 +14,6 @@
1414const DEFAULT_LOCK_LIMIT: u64 = 100;
1515const MAX_LOCK_LIMIT: u64 = 100;
1616
17
18
1917pub async fn create(
2018 svc: &GitService,
2119 space_name: String,
@@ -214,4 +212,3 @@
214212 n => n,
215213 }
216214}
217
Modified lib/git/src/lfs/object.rs View lib/git/src/lfs/object.rs
@@ -23,7 +23,6 @@
2323pub const DOWNLOAD_TTL_SECS: u32 = 3600;
2424pub const MAX_BATCH_OBJECTS: usize = 200;
2525
26
2726pub async fn batch(
2827 svc: &GitService,
2928 space_name: String,
@@ -502,4 +501,3 @@
502501 )
503502 }
504503}
505
Modified lib/git/src/lfs/types.rs View lib/git/src/lfs/types.rs
@@ -141,4 +141,4 @@
141141pub struct LfsUnlockRequest {
142142 #[serde(default)]
143143 pub force: bool,
144}
No newline at end of file
144}
Modified lib/git/src/service/analysis.rs View lib/git/src/service/analysis.rs
@@ -11,8 +11,8 @@
1111use time::OffsetDateTime;
1212use uuid::Uuid;
1313
14use crate::service::cache::{self, IMMUTABLE_TTL};
1514use crate::service::GitService;
15use crate::service::cache::{self, ANALYSIS_TTL};
1616use crate::share::errors::{GitError, map_baby};
1717
1818#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
@@ -122,60 +122,64 @@
122122 am.update(db).await?;
123123 return Ok(Vec::new());
124124 };
125 let langs = language_stats(&baby, &revision).await?;
126 let total: i64 = langs.values().sum();
127 let mut rows: Vec<(String, i64)> = langs.into_iter().collect();
128 rows.sort_by_key(|(_, size)| std::cmp::Reverse(*size));
125 let key = format!("repo.{}.analysis_languages.v1.{}", ctx.model.id, revision);
126 cache::cached_fetch(&ctx.cache, &key, ANALYSIS_TTL, || async {
127 let langs = language_stats(&baby, &revision).await?;
128 let total: i64 = langs.values().sum();
129 let mut rows: Vec<(String, i64)> = langs.into_iter().collect();
130 rows.sort_by_key(|(_, size)| std::cmp::Reverse(*size));
129131
130 let db = self.db_write();
131 git_languages::Entity::delete_many()
132 .filter(git_languages::Column::RepoId.eq(ctx.model.id))
133 .exec(db)
134 .await?;
135 let now = OffsetDateTime::now_utc();
136 let primary = rows.first().map(|(name, _)| name.clone());
137 for (language, size) in &rows {
138 let bp = if total > 0 {
139 ((*size as f64 / total as f64) * 10_000.0).round() as i32
140 } else {
141 0
142 };
143 git_languages::ActiveModel {
144 id: Set(Uuid::now_v7()),
145 repo_id: Set(ctx.model.id),
146 language: Set(language.clone()),
147 percentage_bp: Set(bp),
148 size: Set(*size),
149 created_at: Set(now),
132 let db = self.db_write();
133 git_languages::Entity::delete_many()
134 .filter(git_languages::Column::RepoId.eq(ctx.model.id))
135 .exec(db)
136 .await?;
137 let now = OffsetDateTime::now_utc();
138 let primary = rows.first().map(|(name, _)| name.clone());
139 for (language, size) in &rows {
140 let bp = if total > 0 {
141 ((*size as f64 / total as f64) * 10_000.0).round() as i32
142 } else {
143 0
144 };
145 git_languages::ActiveModel {
146 id: Set(Uuid::now_v7()),
147 repo_id: Set(ctx.model.id),
148 language: Set(language.clone()),
149 percentage_bp: Set(bp),
150 size: Set(*size),
151 created_at: Set(now),
152 }
153 .insert(db)
154 .await?;
150155 }
151 .insert(db)
152 .await?;
153 }
154 let am = repos::ActiveModel {
155 id: Unchanged(ctx.model.id),
156 primary_language: Set(primary),
157 updated_at: Set(now),
158 ..Default::default()
159 };
160 am.update(db).await?;
156 let am = repos::ActiveModel {
157 id: Unchanged(ctx.model.id),
158 primary_language: Set(primary),
159 updated_at: Set(now),
160 ..Default::default()
161 };
162 am.update(db).await?;
161163
162 let primary_name = rows.first().map(|(n, _)| n.clone());
163 Ok(rows
164 .into_iter()
165 .map(|(language, size)| {
166 let is_primary = primary_name.as_deref() == Some(language.as_str());
167 LanguageStatDto {
168 language,
169 percentage_bp: if total > 0 {
170 ((size as f64 / total as f64) * 10_000.0).round() as i32
171 } else {
172 0
173 },
174 size,
175 is_primary,
176 }
177 })
178 .collect())
164 let primary_name = rows.first().map(|(n, _)| n.clone());
165 Ok(rows
166 .into_iter()
167 .map(|(language, size)| {
168 let is_primary = primary_name.as_deref() == Some(language.as_str());
169 LanguageStatDto {
170 language,
171 percentage_bp: if total > 0 {
172 ((size as f64 / total as f64) * 10_000.0).round() as i32
173 } else {
174 0
175 },
176 size,
177 is_primary,
178 }
179 })
180 .collect())
181 })
182 .await
179183 }
180184
181185 pub async fn analysis_languages_read(
@@ -215,11 +219,8 @@
215219 };
216220 let rev_oid = baby.rev_parse(&revision).await.map_err(map_baby)?;
217221 let key = format!("repo.{}.analysis_contributors.v2.{}", ctx.model.id, rev_oid);
218 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
219 let entries = baby
220 .commit_short_stats(&revision)
221 .await
222 .map_err(map_baby)?;
222 cache::cached_fetch(&ctx.cache, &key, ANALYSIS_TTL, || async {
223 let entries = baby.commit_short_stats(&revision).await.map_err(map_baby)?;
223224 let mut map: HashMap<(String, String), (i64, i64, i64)> = HashMap::new();
224225 for e in &entries {
225226 let slot = map
@@ -266,12 +267,12 @@
266267 });
267268 };
268269 let rev_oid = baby.rev_parse(&revision).await.map_err(map_baby)?;
269 let key = format!("repo.{}.analysis_code_activity.v2.{}", ctx.model.id, rev_oid);
270 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
271 let entries = baby
272 .commit_short_stats(&revision)
273 .await
274 .map_err(map_baby)?;
270 let key = format!(
271 "repo.{}.analysis_code_activity.v2.{}",
272 ctx.model.id, rev_oid
273 );
274 cache::cached_fetch(&ctx.cache, &key, ANALYSIS_TTL, || async {
275 let entries = baby.commit_short_stats(&revision).await.map_err(map_baby)?;
275276 let mut authors: Vec<CodeActivityAuthorDto> = Vec::new();
276277 for e in &entries {
277278 match authors.iter_mut().find(|a| a.email == e.author_email) {
Modified lib/git/src/service/blame.rs View lib/git/src/service/blame.rs
@@ -1,8 +1,8 @@
11use gitbaby::blame::types::{BlameHunk, BlameOptions, Range};
22use serde::{Deserialize, Serialize};
33
4use crate::service::GitService;
45use crate::service::cache::{self, IMMUTABLE_TTL};
5use crate::service::GitService;
66use crate::share::errors::{GitError, map_baby};
77
88#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
Modified lib/git/src/service/blobs.rs View lib/git/src/service/blobs.rs
@@ -1,8 +1,8 @@
11use gix::ObjectId;
22use serde::{Deserialize, Serialize};
33
4use crate::service::GitService;
45use crate::service::cache::{self, IMMUTABLE_TTL};
5use crate::service::GitService;
66use crate::share::errors::{GitError, map_baby};
77
88pub const LFS_POINTER_KEYWORD: &str = "https://git-lfs.github.com/spec/v1";
@@ -122,7 +122,10 @@
122122 let object_id = parse_oid(&oid)?;
123123 let ctx = self.context(space_name, repo_name).await?;
124124 let max_blob = self.state.config.git.max_blob_size;
125 let key = format!("repo.{}.blob_content.{}:{}", ctx.model.id, object_id, max_blob);
125 let key = format!(
126 "repo.{}.blob_content.{}:{}",
127 ctx.model.id, object_id, max_blob
128 );
126129 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
127130 let baby = self.baby(&ctx);
128131 let size = baby.get_blob_size(object_id).await.map_err(map_baby)?;
Modified lib/git/src/service/cache.rs View lib/git/src/service/cache.rs
@@ -6,6 +6,7 @@
66use crate::share::errors::GitError;
77
88pub const REV_TTL: Duration = Duration::from_secs(60);
9pub const ANALYSIS_TTL: Duration = Duration::from_secs(600);
910pub const IMMUTABLE_TTL: Duration = Duration::from_secs(30 * 24 * 3600);
1011
1112pub fn sanitize_key(k: &str) -> String {
Modified lib/git/src/service/commits.rs View lib/git/src/service/commits.rs
@@ -14,8 +14,8 @@
1414use uuid::Uuid;
1515
1616use crate::context::facade::GitContext;
17use crate::service::GitService;
1718use crate::service::cache::{self, IMMUTABLE_TTL};
18use crate::service::GitService;
1919use crate::share::errors::{GitError, map_baby};
2020
2121fn default_revision() -> String {
@@ -210,11 +210,8 @@
210210 until: cmd.until,
211211 first_parent: cmd.first_parent,
212212 };
213 let (commits, total): (Vec<CommitDto>, u64) = cache::cached_fetch(
214 &ctx.cache,
215 &page_cache_key,
216 IMMUTABLE_TTL,
217 || async {
213 let (commits, total): (Vec<CommitDto>, u64) =
214 cache::cached_fetch(&ctx.cache, &page_cache_key, IMMUTABLE_TTL, || async {
218215 let total = baby.count_commits(opts.clone()).await.map_err(map_baby)?;
219216 let commits = baby
220217 .list_commits(opts)
@@ -224,9 +221,8 @@
224221 .map(CommitDto::from)
225222 .collect::<Vec<_>>();
226223 Ok((commits, total))
227 },
228 )
229 .await?;
224 })
225 .await?;
230226 let has_more = (skip as u64 + commits.len() as u64) < total;
231227 let diff_view_style = self.user_diff_view_style(viewer).await;
232228 Ok(CommitsPageDto {
@@ -241,10 +237,9 @@
241237
242238 pub async fn user_diff_view_style(&self, viewer: Option<Uuid>) -> String {
243239 if let Some(uid) = viewer
244 && let Ok(Some(row)) =
245 models::users::user_preferences::Entity::find_by_id(uid)
246 .one(self.db_read())
247 .await
240 && let Ok(Some(row)) = models::users::user_preferences::Entity::find_by_id(uid)
241 .one(self.db_read())
242 .await
248243 && let Some(style) = row.diff_view_style
249244 && !style.is_empty()
250245 {
@@ -267,7 +262,10 @@
267262 .as_ref()
268263 .map(|p| p.to_string_lossy().into_owned())
269264 .unwrap_or_default();
270 let key = format!("repo.{}.commit_stats.{}.{}", ctx.model.id, rev_oid, path_str);
265 let key = format!(
266 "repo.{}.commit_stats.{}.{}",
267 ctx.model.id, rev_oid, path_str
268 );
271269 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
272270 baby.commit_stats(&revision, path.as_deref())
273271 .await
Modified lib/git/src/service/conflicts.rs View lib/git/src/service/conflicts.rs
@@ -1,8 +1,8 @@
11use gitbaby::conflict::types::{ConflictFile, ListConflictOptions};
22use serde::{Deserialize, Serialize};
33
4use crate::service::GitService;
45use crate::service::cache::{self, IMMUTABLE_TTL};
5use crate::service::GitService;
66use crate::share::errors::{GitError, map_baby};
77
88#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
@@ -40,7 +40,10 @@
4040 ) -> Result<MergeStatusDto, GitError> {
4141 let ctx = self.context(space_name, repo_name).await?;
4242 let (our_oid, their_oid) = self.resolve_pair(&ctx, &base, &head).await?;
43 let key = format!("repo.{}.conflict_check.{}.{}", ctx.model.id, our_oid, their_oid);
43 let key = format!(
44 "repo.{}.conflict_check.{}.{}",
45 ctx.model.id, our_oid, their_oid
46 );
4447 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
4548 let baby = self.baby(&ctx);
4649 let fast_forwardable = baby.is_ancestor(&base, &head).await.map_err(map_baby)?;
@@ -88,7 +91,10 @@
8891 ) -> Result<Vec<String>, GitError> {
8992 let ctx = self.context(space_name, repo_name).await?;
9093 let (our_oid, their_oid) = self.resolve_pair(&ctx, &base, &head).await?;
91 let key = format!("repo.{}.conflict_files.{}.{}", ctx.model.id, our_oid, their_oid);
94 let key = format!(
95 "repo.{}.conflict_files.{}.{}",
96 ctx.model.id, our_oid, their_oid
97 );
9298 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
9399 let baby = self.baby(&ctx);
94100 let files = baby
Modified lib/git/src/service/diffs.rs View lib/git/src/service/diffs.rs
@@ -5,8 +5,8 @@
55use gix::ObjectId;
66use serde::{Deserialize, Serialize};
77
8use crate::service::GitService;
89use crate::service::cache::{self, IMMUTABLE_TTL};
9use crate::service::GitService;
1010use crate::share::errors::{GitError, map_baby};
1111
1212pub fn parse_oid(hex: &str) -> Result<ObjectId, GitError> {
@@ -142,7 +142,10 @@
142142 let baby = self.baby(&ctx);
143143 let left = baby.rev_parse(&base).await.map_err(map_baby)?;
144144 let right = baby.rev_parse(&head).await.map_err(map_baby)?;
145 let key = format!("repo.{}.diff_changed_paths.{}.{}", ctx.model.id, left, right);
145 let key = format!(
146 "repo.{}.diff_changed_paths.{}.{}",
147 ctx.model.id, left, right
148 );
146149 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
147150 let paths = baby
148151 .find_changed_paths(
@@ -170,7 +173,10 @@
170173 ) -> Result<ShortStat, GitError> {
171174 let ctx = self.context(space_name, repo_name).await?;
172175 let req = self.diff_request(&ctx, &base, &head).await?;
173 let key = format!("repo.{}.diff_short_stat.{}.{}", ctx.model.id, req.left, req.right);
176 let key = format!(
177 "repo.{}.diff_short_stat.{}.{}",
178 ctx.model.id, req.left, req.right
179 );
174180 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
175181 self.baby(&ctx)
176182 .shortstat(&req, &DiffOptions::default())
@@ -239,16 +245,20 @@
239245 const EMPTY_TREE: &str = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
240246 let baby = self.baby(ctx);
241247 let head_oid = baby.rev_parse(commit).await.map_err(map_baby)?;
242 let info = baby.get_commit(&head_oid.to_string()).await.map_err(map_baby)?;
248 let info = baby
249 .get_commit(&head_oid.to_string())
250 .await
251 .map_err(map_baby)?;
243252 let base = info
244253 .parents
245254 .first()
246255 .map(|p| p.to_string())
247256 .unwrap_or_else(|| EMPTY_TREE.to_string());
248 let req = self
249 .diff_request(ctx, &base, &head_oid.to_string())
250 .await?;
251 let key = format!("repo.{}.diff_files_dto.{}.{:?}", ctx.model.id, req.left, None::<Vec<std::path::PathBuf>>);
257 let req = self.diff_request(ctx, &base, &head_oid.to_string()).await?;
258 let key = format!(
259 "repo.{}.diff_files_dto.{}.{:?}",
260 ctx.model.id, req.left, None::<Vec<std::path::PathBuf>>
261 );
252262 let files = cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
253263 let list = baby
254264 .commit_diff(&req, &DiffOptions::default(), None)
Modified lib/git/src/service/gitcmd.rs View lib/git/src/service/gitcmd.rs
@@ -115,4 +115,4 @@
115115 cmd.run()
116116 .await
117117 .map_err(|e| GitError::Internal(format!("{label} failed: {e}")))
118}
No newline at end of file
118}
Modified lib/git/src/service/licenses.rs View lib/git/src/service/licenses.rs
@@ -1,7 +1,5 @@
11use models::repo::repo_license;
2use sea_orm::{
3 ActiveModelTrait, EntityTrait, QueryOrder, QuerySelect, Set,
4};
2use sea_orm::{ActiveModelTrait, EntityTrait, QueryOrder, QuerySelect, Set};
53use serde::{Deserialize, Serialize};
64use time::OffsetDateTime;
75use utoipa::ToSchema;
@@ -51,7 +49,11 @@
5149 Ok(row.into())
5250 }
5351
54 pub async fn list_licenses(&self, limit: u64, offset: u64) -> Result<Vec<LicenseDto>, GitError> {
52 pub async fn list_licenses(
53 &self,
54 limit: u64,
55 offset: u64,
56 ) -> Result<Vec<LicenseDto>, GitError> {
5557 let rows = repo_license::Entity::find()
5658 .order_by_asc(repo_license::Column::Name)
5759 .limit(limit)
@@ -67,13 +69,19 @@
6769 cmd: UpsertLicenseCmd,
6870 ) -> Result<LicenseDto, GitError> {
6971 if name.is_empty() {
70 return Err(GitError::InvalidArgument("license name must not be empty".to_string()));
72 return Err(GitError::InvalidArgument(
73 "license name must not be empty".to_string(),
74 ));
7175 }
7276 if cmd.display_name.is_empty() {
73 return Err(GitError::InvalidArgument("license display_name must not be empty".to_string()));
77 return Err(GitError::InvalidArgument(
78 "license display_name must not be empty".to_string(),
79 ));
7480 }
7581 if cmd.body.is_empty() {
76 return Err(GitError::InvalidArgument("license body must not be empty".to_string()));
82 return Err(GitError::InvalidArgument(
83 "license body must not be empty".to_string(),
84 ));
7785 }
7886 let now = OffsetDateTime::now_utc();
7987 let existing = repo_license::Entity::find_by_id(name.to_string())
@@ -108,4 +116,4 @@
108116 .await?;
109117 Ok(row.is_some())
110118 }
111}
No newline at end of file
119}
Modified lib/git/src/service/logs.rs View lib/git/src/service/logs.rs
@@ -1,8 +1,8 @@
11use gitbaby::commit::types::{CommitInfo, ListCommitsOptions};
22use serde::{Deserialize, Serialize};
33
4use crate::service::GitService;
45use crate::service::cache::{self, IMMUTABLE_TTL};
5use crate::service::GitService;
66use crate::share::errors::{GitError, map_baby};
77
88#[derive(Debug, Clone, Serialize, Deserialize)]
Modified lib/git/src/service/mod.rs View lib/git/src/service/mod.rs
@@ -42,8 +42,8 @@
4242pub mod diffs;
4343pub mod gitcmd;
4444pub mod hooks;
45pub mod lfs;
4546pub mod licenses;
46pub mod lfs;
4747pub mod logs;
4848pub mod maintenance;
4949pub mod packs;
Modified lib/git/src/service/patches.rs View lib/git/src/service/patches.rs
@@ -1,9 +1,9 @@
11use serde::{Deserialize, Serialize};
22use time::OffsetDateTime;
33
4use crate::service::GitService;
45use crate::service::cache::{self, IMMUTABLE_TTL};
56use crate::service::gitcmd::run_git_soft;
6use crate::service::GitService;
77use crate::share::errors::{GitError, map_baby};
88
99#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
@@ -40,13 +40,7 @@
4040 let key = format!("repo.{}.patch_generate.{}.{}", ctx.model.id, left, right);
4141 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
4242 let body = baby
43 .raw_diff(
44 &gitbaby::diff::types::DiffRequest {
45 left,
46 right,
47 },
48 None,
49 )
43 .raw_diff(&gitbaby::diff::types::DiffRequest { left, right }, None)
5044 .await
5145 .map_err(map_baby)?;
5246 let size = body.len() as i64;
Modified lib/git/src/service/path_log.rs View lib/git/src/service/path_log.rs
@@ -3,8 +3,8 @@
33use gitbaby::commit::types::ListCommitsOptions;
44use serde::{Deserialize, Serialize};
55
6use crate::service::GitService;
67use crate::service::cache::{self, IMMUTABLE_TTL};
7use crate::service::GitService;
88use crate::share::errors::{GitError, map_baby};
99
1010#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
@@ -90,7 +90,12 @@
9090 .map_err(map_baby)?
9191 .to_string();
9292
93 let key = format!("repo.{}.path_log.{}.{}", ctx.model.id, oid, target_dir.display());
93 let key = format!(
94 "repo.{}.path_log.{}.{}",
95 ctx.model.id,
96 oid,
97 target_dir.display()
98 );
9499 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
95100 let entries = baby
96101 .get_tree_entries(&oid, &target_dir)
Modified lib/git/src/service/remotes.rs View lib/git/src/service/remotes.rs
@@ -132,14 +132,7 @@
132132 args.push(r);
133133 }
134134 let timeout = self.state.config.git.git_command_timeout_secs;
135 let out = run_git_soft(
136 "remotes::push",
137 &ctx.repo_dir(),
138 args,
139 &env,
140 timeout,
141 )
142 .await?;
135 let out = run_git_soft("remotes::push", &ctx.repo_dir(), args, &env, timeout).await?;
143136 let stdout = String::from_utf8_lossy(&out.stdout);
144137 let pushed: Vec<String> = stdout
145138 .lines()
Modified lib/git/src/service/signatures.rs View lib/git/src/service/signatures.rs
@@ -1,8 +1,8 @@
11use serde::{Deserialize, Serialize};
22
3use crate::service::GitService;
34use crate::service::cache::{self, IMMUTABLE_TTL};
45use crate::service::gitcmd::run_git_soft;
5use crate::service::GitService;
66use crate::share::errors::{GitError, map_baby};
77
88#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
Modified lib/git/src/service/stars.rs View lib/git/src/service/stars.rs
@@ -1,13 +1,13 @@
1use models::repo::repo_star;
12use models::repo::repos;
2use models::repo::repo_star;
33use sea_orm::{
44 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set,
55 TransactionTrait, Unchanged,
66};
77use serde::{Deserialize, Serialize};
88use time::OffsetDateTime;
9use utoipa::ToSchema;
910use uuid::Uuid;
10use utoipa::ToSchema;
1111
1212use crate::context::facade::GitContext;
1313use crate::service::GitService;
@@ -107,11 +107,7 @@
107107 Ok(RepoStarResp { stars: after })
108108 }
109109
110 pub async fn is_starred(
111 &self,
112 ctx: &GitContext,
113 user_id: Uuid,
114 ) -> Result<bool, GitError> {
110 pub async fn is_starred(&self, ctx: &GitContext, user_id: Uuid) -> Result<bool, GitError> {
115111 let row = repo_star::Entity::find()
116112 .filter(repo_star::Column::UserId.eq(user_id))
117113 .filter(repo_star::Column::RepoId.eq(ctx.model.id))
@@ -156,4 +152,4 @@
156152 })
157153 .collect())
158154 }
159}
No newline at end of file
155}
Modified lib/git/src/service/submodules.rs View lib/git/src/service/submodules.rs
@@ -3,8 +3,8 @@
33use gitbaby::commit::types::TreeEntryMode;
44use serde::{Deserialize, Serialize};
55
6use crate::service::GitService;
67use crate::service::cache::{self, IMMUTABLE_TTL};
7use crate::service::GitService;
88use crate::share::errors::{GitError, map_baby};
99
1010#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
@@ -66,7 +66,10 @@
6666 let ctx = self.context(space_name, repo_name).await?;
6767 let baby = self.baby(&ctx);
6868 let rev_oid = baby.rev_parse(&revision).await.map_err(map_baby)?;
69 let key = format!("repo.{}.submodule_resolve.{}.{}", ctx.model.id, rev_oid, path);
69 let key = format!(
70 "repo.{}.submodule_resolve.{}.{}",
71 ctx.model.id, rev_oid, path
72 );
7073 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
7174 let entries = baby
7275 .get_tree_entries(&revision, std::path::Path::new(""))
Modified lib/git/src/service/tags.rs View lib/git/src/service/tags.rs
@@ -11,8 +11,8 @@
1111use time::OffsetDateTime;
1212
1313use crate::context::facade::GitContext;
14use crate::service::GitService;
1415use crate::service::cache::{self, IMMUTABLE_TTL};
15use crate::service::GitService;
1616use crate::share::errors::{GitError, map_baby};
1717use crate::share::policy::load_tag_policy;
1818
@@ -135,8 +135,9 @@
135135 stderr.trim()
136136 )));
137137 }
138 parse_cat_file(&stdout, &oid)
139 .ok_or_else(|| GitError::Internal(format!("parse tag object {oid} for `{name}` failed")))
138 parse_cat_file(&stdout, &oid).ok_or_else(|| {
139 GitError::Internal(format!("parse tag object {oid} for `{name}` failed"))
140 })
140141 })
141142 .await
142143}
Modified lib/git/src/service/topics.rs View lib/git/src/service/topics.rs
@@ -6,8 +6,8 @@
66};
77use serde::{Deserialize, Serialize};
88use time::OffsetDateTime;
9use utoipa::ToSchema;
910use uuid::Uuid;
10use utoipa::ToSchema;
1111
1212use crate::context::facade::GitContext;
1313use crate::service::GitService;
@@ -49,7 +49,9 @@
4949 pub async fn create_topic(&self, name: String) -> Result<TopicDto, GitError> {
5050 let trimmed = name.trim().to_string();
5151 if trimmed.is_empty() {
52 return Err(GitError::InvalidArgument("topic name must not be empty".to_string()));
52 return Err(GitError::InvalidArgument(
53 "topic name must not be empty".to_string(),
54 ));
5355 }
5456 let now = OffsetDateTime::now_utc();
5557 let existing = repo_topic::Entity::find()
@@ -145,10 +147,7 @@
145147 Ok(())
146148 }
147149
148 pub async fn list_repo_topics(
149 &self,
150 ctx: &GitContext,
151 ) -> Result<Vec<TopicDto>, GitError> {
150 pub async fn list_repo_topics(&self, ctx: &GitContext) -> Result<Vec<TopicDto>, GitError> {
152151 let relations = repo_relation_topic::Entity::find()
153152 .filter(repo_relation_topic::Column::RepoId.eq(ctx.model.id))
154153 .all(self.db_read())
@@ -199,4 +198,4 @@
199198 })
200199 .collect())
201200 }
202}
No newline at end of file
201}
Modified lib/git/src/service/trees.rs View lib/git/src/service/trees.rs
@@ -4,8 +4,8 @@
44use gitbaby::tree::types::WalkTreeOptions;
55use serde::{Deserialize, Serialize};
66
7use crate::service::GitService;
78use crate::service::cache::{self, IMMUTABLE_TTL};
8use crate::service::GitService;
99use crate::share::errors::{GitError, map_baby};
1010use crate::share::validate::validate_revision;
1111
@@ -37,7 +37,12 @@
3737 let ctx = self.context(space_name, repo_name).await?;
3838 let baby = self.baby(&ctx);
3939 let rev_oid = baby.rev_parse(&revision).await.map_err(map_baby)?;
40 let key = format!("repo.{}.tree_read.{}.{}", ctx.model.id, rev_oid, path.display());
40 let key = format!(
41 "repo.{}.tree_read.{}.{}",
42 ctx.model.id,
43 rev_oid,
44 path.display()
45 );
4146 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
4247 baby.get_tree_entries(&revision, &path)
4348 .await
@@ -157,7 +162,10 @@
157162 let baby = self.baby(&ctx);
158163 let rev_oid = baby.rev_parse(&revision).await.map_err(map_baby)?;
159164 let path_str = path.as_deref().unwrap_or("");
160 let key = format!("repo.{}.tree_last_updated.{}.{}", ctx.model.id, rev_oid, path_str);
165 let key = format!(
166 "repo.{}.tree_last_updated.{}.{}",
167 ctx.model.id, rev_oid, path_str
168 );
161169 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
162170 let rows = baby
163171 .tree_entries_with_latest(&revision, path.as_deref())
@@ -200,12 +208,19 @@
200208 path: &str,
201209 ) -> Result<Vec<gitbaby::tree::types::TreeEntryWithLatest>, GitError> {
202210 validate_revision(revision)?;
203 if path.contains("..") || path.contains('\\') || path.starts_with('/') || path.contains('\0') {
211 if path.contains("..")
212 || path.contains('\\')
213 || path.starts_with('/')
214 || path.contains('\0')
215 {
204216 return Err(GitError::InvalidArgument("invalid path".into()));
205217 }
206218 let baby = self.baby(ctx);
207219 let rev_oid = baby.rev_parse(revision).await.map_err(map_baby)?;
208 let key = format!("repo.{}.tree_dir_entries_html.{}.{}", ctx.model.id, rev_oid, path);
220 let key = format!(
221 "repo.{}.tree_dir_entries_html.{}.{}",
222 ctx.model.id, rev_oid, path
223 );
209224 cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
210225 let p = if path.is_empty() { None } else { Some(path) };
211226 baby.tree_dir_entries_with_latest(revision, p)
Modified lib/git/src/service/watches.rs View lib/git/src/service/watches.rs
@@ -1,14 +1,14 @@
11use models::common::watch_mode::WatchMode;
2use models::repo::repo_watch;
23use models::repo::repos;
3use models::repo::repo_watch;
44use sea_orm::{
55 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set,
66 TransactionTrait, Unchanged,
77};
88use serde::{Deserialize, Serialize};
99use time::OffsetDateTime;
10use utoipa::ToSchema;
1011use uuid::Uuid;
11use utoipa::ToSchema;
1212
1313use crate::context::facade::GitContext;
1414use crate::service::GitService;
@@ -96,11 +96,7 @@
9696 })
9797 }
9898
99 pub async fn unwatch(
100 &self,
101 ctx: &GitContext,
102 user_id: Uuid,
103 ) -> Result<(), GitError> {
99 pub async fn unwatch(&self, ctx: &GitContext, user_id: Uuid) -> Result<(), GitError> {
104100 let db = self.db_write();
105101 let repo_id = ctx.model.id;
106102 let now = OffsetDateTime::now_utc();
@@ -183,4 +179,4 @@
183179 })
184180 .collect())
185181 }
186}
No newline at end of file
182}
Modified lib/git/src/service/whitelists.rs View lib/git/src/service/whitelists.rs
@@ -2,13 +2,11 @@
22use models::repo::repo_branch_protection_whitelists::{self, WhitelistKind};
33use models::repo::repo_tag_protection_policy;
44use models::repo::repo_tag_protection_whitelists;
5use sea_orm::{
6 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set, TransactionTrait,
7};
5use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set, TransactionTrait};
86use serde::{Deserialize, Serialize};
97use time::OffsetDateTime;
10use uuid::Uuid;
118use utoipa::ToSchema;
9use uuid::Uuid;
1210
1311use crate::service::GitService;
1412use crate::share::errors::GitError;
@@ -64,14 +62,20 @@
6462}
6563
6664impl GitService {
67 async fn ensure_branch_policy(&self, policy_id: Uuid) -> Result<repo_branch_protection_policy::Model, GitError> {
65 async fn ensure_branch_policy(
66 &self,
67 policy_id: Uuid,
68 ) -> Result<repo_branch_protection_policy::Model, GitError> {
6869 repo_branch_protection_policy::Entity::find_by_id(policy_id)
6970 .one(self.db_read())
7071 .await?
7172 .ok_or(GitError::NotFound)
7273 }
7374
74 async fn ensure_tag_policy(&self, policy_id: Uuid) -> Result<repo_tag_protection_policy::Model, GitError> {
75 async fn ensure_tag_policy(
76 &self,
77 policy_id: Uuid,
78 ) -> Result<repo_tag_protection_policy::Model, GitError> {
7579 repo_tag_protection_policy::Entity::find_by_id(policy_id)
7680 .one(self.db_read())
7781 .await?
@@ -151,9 +155,7 @@
151155 user_id: Set(cmd.user_id),
152156 };
153157 let saved = TransactionTrait::transaction(self.db_write(), |txn| {
154 Box::pin(async move {
155 am.insert(txn).await.map_err(GitError::from)
156 })
158 Box::pin(async move { am.insert(txn).await.map_err(GitError::from) })
157159 })
158160 .await?;
159161 Ok(saved.into())
@@ -181,4 +183,4 @@
181183 .await?;
182184 Ok(rows.into_iter().map(Into::into).collect())
183185 }
184}
No newline at end of file
186}
Modified lib/git/src/share/access.rs View lib/git/src/share/access.rs
@@ -119,10 +119,7 @@
119119 Ok(uid)
120120}
121121
122async fn resolve_caller_for_write(
123 svc: &GitService,
124 auth: &BasicAuth,
125) -> Result<Uuid, GitError> {
122async fn resolve_caller_for_write(svc: &GitService, auth: &BasicAuth) -> Result<Uuid, GitError> {
126123 let Some(token) = &auth.token else {
127124 return Err(GitError::Unauthorized);
128125 };
@@ -244,4 +241,4 @@
244241 diff |= x ^ y;
245242 }
246243 diff == 0
247}
No newline at end of file
244}
Modified lib/git/src/share/policy.rs View lib/git/src/share/policy.rs
@@ -92,7 +92,9 @@
9292 return Ok(WhitelistSet::default());
9393 }
9494 let rows = repo_branch_protection_whitelists::Entity::find()
95 .filter(repo_branch_protection_whitelists::Column::PolicyId.is_in(policy_ids.iter().copied()))
95 .filter(
96 repo_branch_protection_whitelists::Column::PolicyId.is_in(policy_ids.iter().copied()),
97 )
9698 .all(db)
9799 .await?;
98100 let mut out = WhitelistSet::default();
@@ -125,4 +127,4 @@
125127 out.merge_allowed.push(row.user_id);
126128 }
127129 Ok(out)
128}
No newline at end of file
130}
Modified lib/git/src/share/validate.rs View lib/git/src/share/validate.rs
@@ -189,14 +189,10 @@
189189pub fn validate_refspec(refspec: &str) -> Result<(), GitError> {
190190 let r = refspec.trim();
191191 if r.is_empty() || r.len() > 1024 || r.starts_with('-') {
192 return Err(GitError::InvalidArgument(format!(
193 "invalid refspec `{r}`"
194 )));
192 return Err(GitError::InvalidArgument(format!("invalid refspec `{r}`")));
195193 }
196194 if r.chars().any(|c| c.is_control()) {
197 return Err(GitError::InvalidArgument(format!(
198 "invalid refspec `{r}`"
199 )));
195 return Err(GitError::InvalidArgument(format!("invalid refspec `{r}`")));
200196 }
201197 Ok(())
202198}
Modified lib/git/src/smart/dispatch.rs View lib/git/src/smart/dispatch.rs
@@ -76,18 +76,19 @@
7676 if is_receive && actor_id.is_some() {
7777 let updates = parse_receive_pack_refs(&input)?;
7878 if !updates.is_empty()
79 && let Some(actor_uid) = actor_id {
80 let baby = service.git.baby(&ctx);
81 let verdict = pre_receive::validate_pre_receive(
82 service.git.db_read(),
83 &baby,
84 ctx.model.id,
85 actor_uid,
86 &updates,
87 )
88 .await?;
89 verdict.accepted_only()?;
90 }
79 && let Some(actor_uid) = actor_id
80 {
81 let baby = service.git.baby(&ctx);
82 let verdict = pre_receive::validate_pre_receive(
83 service.git.db_read(),
84 &baby,
85 ctx.model.id,
86 actor_uid,
87 &updates,
88 )
89 .await?;
90 verdict.accepted_only()?;
91 }
9192 if let Err(e) = crate::sync::post_receive::prepare_post_receive(&bare) {
9293 tracing::warn!(?e, "post-receive hook setup failed");
9394 }
Modified lib/git/src/smart/error.rs View lib/git/src/smart/error.rs
@@ -39,10 +39,9 @@
3939
4040 pub fn message(&self) -> String {
4141 match self {
42 Self::BadRequest(m)
43 | Self::Forbidden(m)
44 | Self::NotFound(m)
45 | Self::Conflict(m) => m.clone(),
42 Self::BadRequest(m) | Self::Forbidden(m) | Self::NotFound(m) | Self::Conflict(m) => {
43 m.clone()
44 }
4645 Self::Internal(m) => {
4746 tracing::warn!(error = %m, "smart http internal error");
4847 "internal server error".to_string()
@@ -78,4 +77,4 @@
7877 }
7978 resp
8079 }
81}
No newline at end of file
80}
Modified lib/git/src/smart/handler.rs View lib/git/src/smart/handler.rs
@@ -7,12 +7,12 @@
77use axum::response::{IntoResponse, Response};
88
99use crate::share::access::{BasicAuth, allow_repo_read};
10use crate::smart::dispatch;
1011use crate::smart::error::{SmartError, SmartResult};
1112use crate::smart::lfs;
1213use crate::smart::object;
1314use crate::smart::rpc::GitService;
1415use crate::smart::service::SmartService;
15use crate::smart::dispatch;
1616
1717pub fn router(service: SmartService) -> Router {
1818 Router::new()
@@ -90,7 +90,17 @@
9090 .await
9191 }
9292 (p, _) if p.starts_with("info/lfs/") => {
93 lfs::dispatch(&service, &space, &repo_name, &headers, p, method.clone(), body, &auth).await
93 lfs::dispatch(
94 &service,
95 &space,
96 &repo_name,
97 &headers,
98 p,
99 method.clone(),
100 body,
101 &auth,
102 )
103 .await
94104 }
95105 (_, Method::GET) => {
96106 let resp = match service.resolve(&space, &repo_name).await {
@@ -111,7 +121,7 @@
111121 Err(e) => e.into_response(),
112122 };
113123 Ok(resp)
114 },
124 }
115125 _ => Ok(SmartError::BadRequest("method not allowed".into()).into_response()),
116126 };
117127 result.unwrap_or_else(|e| e.into_response())
Modified lib/git/src/smart/info_refs.rs View lib/git/src/smart/info_refs.rs
@@ -40,4 +40,4 @@
4040 }
4141 Err(e) => Err(SmartError::Io(e)),
4242 }
43}
No newline at end of file
43}
Modified lib/git/src/smart/lfs.rs View lib/git/src/smart/lfs.rs
@@ -40,7 +40,9 @@
4040 p if p.starts_with("info/lfs/objects/") => {
4141 let oid = &p["info/lfs/objects/".len()..];
4242 match method {
43 Method::PUT => upload(service, space, repo, oid.to_string(), headers, body, auth).await,
43 Method::PUT => {
44 upload(service, space, repo, oid.to_string(), headers, body, auth).await
45 }
4446 Method::GET => download(service, space, repo, oid, auth).await,
4547 _ => Ok(SmartError::BadRequest("method not allowed".into()).into_response()),
4648 }
@@ -52,7 +54,10 @@
5254 "info/lfs/locks/verify" if method == Method::POST => {
5355 verify_locks(service, space, repo, body, auth).await
5456 }
55 p if p.starts_with("info/lfs/locks/") && p.ends_with("/unlock") && method == Method::POST => {
57 p if p.starts_with("info/lfs/locks/")
58 && p.ends_with("/unlock")
59 && method == Method::POST =>
60 {
5661 let id = &p["info/lfs/locks/".len()..p.len() - "/unlock".len()];
5762 unlock(service, space, repo, id.to_string(), auth).await
5863 }
@@ -206,11 +211,20 @@
206211 .map_err(|e| SmartError::BadRequest(format!("read body: {e}")))?;
207212 let req: LfsLockCreateRequest = serde_json::from_slice(&bytes)
208213 .map_err(|e| SmartError::BadRequest(format!("invalid lock request: {e}")))?;
209 let lock = crate::lfs::create_lock(svc, space.to_string(), repo.to_string(), uid, req.path, None).await?;
214 let lock = crate::lfs::create_lock(
215 svc,
216 space.to_string(),
217 repo.to_string(),
218 uid,
219 req.path,
220 None,
221 )
222 .await?;
210223 Ok((
211224 StatusCode::CREATED,
212225 [(header::CONTENT_TYPE, "application/vnd.git-lfs+json")],
213 serde_json::to_vec(&json!({ "lock": lock })).map_err(|e| SmartError::Internal(format!("serialize: {e}")))?,
226 serde_json::to_vec(&json!({ "lock": lock }))
227 .map_err(|e| SmartError::Internal(format!("serialize: {e}")))?,
214228 )
215229 .into_response())
216230}
@@ -257,7 +271,15 @@
257271 let svc = &service.git;
258272 let ctx = service.resolve(space, repo).await?;
259273 let uid = require_writer(svc, auth, &ctx).await?;
260 crate::lfs::unlock_by_id(svc, space.to_string(), repo.to_string(), uid, lock_id, false).await?;
274 crate::lfs::unlock_by_id(
275 svc,
276 space.to_string(),
277 repo.to_string(),
278 uid,
279 lock_id,
280 false,
281 )
282 .await?;
261283 Ok((
262284 StatusCode::OK,
263285 [(header::CONTENT_TYPE, "application/vnd.git-lfs+json")],
@@ -267,12 +289,8 @@
267289 .into_response())
268290}
269291
270async fn read_body(
271 _headers: &HeaderMap,
272 body: Body,
273 limit: usize,
274) -> SmartResult<bytes::Bytes> {
292async fn read_body(_headers: &HeaderMap, body: Body, limit: usize) -> SmartResult<bytes::Bytes> {
275293 axum::body::to_bytes(body, limit)
276294 .await
277295 .map_err(|e| SmartError::BadRequest(format!("failed to read body: {e}")))
278}
No newline at end of file
296}
Modified lib/git/src/smart/mod.rs View lib/git/src/smart/mod.rs
@@ -16,4 +16,4 @@
1616pub use pre_receive::{PreReceiveVerdict, RefUpdate, RefVerdict};
1717pub use push::{NoopPushBackend, PushCoordinator, PushOperations, RedisPushBackend};
1818pub use rpc::GitService;
19pub use service::SmartService;
No newline at end of file
19pub use service::SmartService;
Modified lib/git/src/smart/object.rs View lib/git/src/smart/object.rs
@@ -5,10 +5,7 @@
55
66use crate::smart::error::{SmartError, SmartResult};
77
8pub async fn serve(
9 bare: &Path,
10 request_path: &str,
11) -> SmartResult<Response> {
8pub async fn serve(bare: &Path, request_path: &str) -> SmartResult<Response> {
129 let file = resolve_within_root(bare, request_path)?;
1310 match tokio::fs::read(&file).await {
1411 Ok(bytes) => Ok((
@@ -17,9 +14,9 @@
1714 bytes,
1815 )
1916 .into_response()),
20 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
21 Err(SmartError::NotFound(format!("file not found: {request_path}")))
22 }
17 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(SmartError::NotFound(format!(
18 "file not found: {request_path}"
19 ))),
2320 Err(e) => Err(SmartError::Io(e)),
2421 }
2522}
@@ -54,4 +51,4 @@
5451 } else {
5552 "application/octet-stream"
5653 }
57}
No newline at end of file
54}
Modified lib/git/src/smart/pktline.rs View lib/git/src/smart/pktline.rs
@@ -5,9 +5,7 @@
55pub fn encode_line(line: &[u8]) -> SmartResult<Vec<u8>> {
66 let len = line.len() + 4;
77 if len > MAX_PKT_SIZE {
8 return Err(SmartError::BadRequest(format!(
9 "pkt-line too long: {len}"
10 )));
8 return Err(SmartError::BadRequest(format!("pkt-line too long: {len}")));
119 }
1210 let mut out = Vec::with_capacity(4 + line.len());
1311 out.extend_from_slice(format!("{len:04x}").as_bytes());
@@ -32,7 +30,8 @@
3230 while pos + 4 <= input.len() {
3331 let header = &input[pos..pos + 4];
3432 let len = usize::from_str_radix(
35 std::str::from_utf8(header).map_err(|_| SmartError::BadRequest("invalid pkt-line length header".into()))?,
33 std::str::from_utf8(header)
34 .map_err(|_| SmartError::BadRequest("invalid pkt-line length header".into()))?,
3635 16,
3736 )
3837 .map_err(|_| SmartError::BadRequest("invalid pkt-line length header".into()))?;
@@ -41,7 +40,9 @@
4140 break;
4241 }
4342 if !(4..=MAX_PKT_SIZE).contains(&len) {
44 return Err(SmartError::BadRequest(format!("invalid pkt-line length: {len}")));
43 return Err(SmartError::BadRequest(format!(
44 "invalid pkt-line length: {len}"
45 )));
4546 }
4647 let payload_end = pos + len - 4;
4748 if payload_end > input.len() {
@@ -51,4 +52,4 @@
5152 pos = payload_end;
5253 }
5354 Ok(lines)
54}
No newline at end of file
55}
Modified lib/git/src/smart/pre_receive.rs View lib/git/src/smart/pre_receive.rs
@@ -4,7 +4,7 @@
44use uuid::Uuid;
55
66use crate::share::policy::{
7 load_branch_whitelists, load_tag_whitelists, WhitelistSet, WhitelistSubject,
7 WhitelistSet, WhitelistSubject, load_branch_whitelists, load_tag_whitelists,
88};
99use crate::smart::error::{SmartError, SmartResult};
1010use crate::smart::pktline;
@@ -148,15 +148,21 @@
148148 whitelists: &WhitelistSet,
149149 zero_oid: &str,
150150) -> SmartResult<RefVerdict> {
151 let matchers: Vec<(&models::repo::repo_branch_protection_policy::Model, GlobMatcher)> =
152 match branch_policies
153 .iter()
154 .filter_map(|m| Glob::new(&m.branch_pattern).ok().map(|g| (m, g.compile_matcher())))
155 .find(|(_, matcher)| matcher.is_match(branch))
156 {
157 Some(found) => vec![found],
158 None => Vec::new(),
159 };
151 let matchers: Vec<(
152 &models::repo::repo_branch_protection_policy::Model,
153 GlobMatcher,
154 )> = match branch_policies
155 .iter()
156 .filter_map(|m| {
157 Glob::new(&m.branch_pattern)
158 .ok()
159 .map(|g| (m, g.compile_matcher()))
160 })
161 .find(|(_, matcher)| matcher.is_match(branch))
162 {
163 Some(found) => vec![found],
164 None => Vec::new(),
165 };
160166 if matchers.is_empty() {
161167 return Ok(RefVerdict::Accept);
162168 }
@@ -222,11 +228,7 @@
222228/// Real fast-forward check via `git merge-base --is-ancestor old new`.
223229/// `old == new` (no-op push) and `old == zero_oid` (new branch) are not
224230/// ancestors checks — they short-circuit to true without spawning git.
225async fn is_fast_forward(
226 baby: &gitbaby::GitBaby,
227 old: &str,
228 new: &str,
229) -> SmartResult<bool> {
231async fn is_fast_forward(baby: &gitbaby::GitBaby, old: &str, new: &str) -> SmartResult<bool> {
230232 if old == new {
231233 return Ok(true);
232234 }
@@ -248,9 +250,8 @@
248250}
249251
250252pub fn parse_receive_pack_refs(input: &[u8]) -> SmartResult<Vec<RefUpdate>> {
251 let lines = pktline::decode_stream(input).map_err(|e| {
252 SmartError::BadRequest(format!("invalid receive-pack request: {e}"))
253 })?;
253 let lines = pktline::decode_stream(input)
254 .map_err(|e| SmartError::BadRequest(format!("invalid receive-pack request: {e}")))?;
254255 let mut out = Vec::new();
255256 for mut line in lines {
256257 if let Some(nul) = line.iter().position(|b| *b == 0) {
@@ -259,8 +260,9 @@
259260 if line.is_empty() {
260261 continue;
261262 }
262 let s = std::str::from_utf8(&line)
263 .map_err(|_| SmartError::BadRequest("receive-pack ref line is not valid UTF-8".to_string()))?;
263 let s = std::str::from_utf8(&line).map_err(|_| {
264 SmartError::BadRequest("receive-pack ref line is not valid UTF-8".to_string())
265 })?;
264266 let mut parts = s.splitn(3, ' ');
265267 let old = parts.next().unwrap_or_default();
266268 let new = parts.next().unwrap_or_default();
@@ -296,4 +298,4 @@
296298#[allow(dead_code)]
297299pub fn update_pkts(input: &Bytes) -> SmartResult<Vec<RefUpdate>> {
298300 parse_receive_pack_refs(input)
299}
No newline at end of file
301}
Modified lib/git/src/smart/push.rs View lib/git/src/smart/push.rs
@@ -25,10 +25,8 @@
2525 };
2626}
2727
28const RENEW_SCRIPT: &str =
29 "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('SET', KEYS[1], ARGV[1], 'EX', ARGV[2]) else return 0 end";
30const RELEASE_SCRIPT: &str =
31 "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";
28const RENEW_SCRIPT: &str = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('SET', KEYS[1], ARGV[1], 'EX', ARGV[2]) else return 0 end";
29const RELEASE_SCRIPT: &str = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";
3230
3331pub struct RedisPushBackend {
3432 pub kv: Arc<AppRedis>,
@@ -65,12 +63,17 @@
6563 }
6664
6765 async fn renew(&self, key: &str, token: &[u8], ttl_secs: u64) -> SmartResult<bool> {
68 self.eval_bool(RENEW_SCRIPT, key, vec![token.to_vec(), ttl_secs.to_string().into_bytes()])
69 .await
66 self.eval_bool(
67 RENEW_SCRIPT,
68 key,
69 vec![token.to_vec(), ttl_secs.to_string().into_bytes()],
70 )
71 .await
7072 }
7173
7274 async fn release(&self, key: &str, token: &[u8]) -> SmartResult<()> {
73 self.eval_bool(RELEASE_SCRIPT, key, vec![token.to_vec()]).await?;
75 self.eval_bool(RELEASE_SCRIPT, key, vec![token.to_vec()])
76 .await?;
7477 Ok(())
7578 }
7679
@@ -107,12 +110,7 @@
107110
108111#[async_trait]
109112impl PushOperations for NoopPushBackend {
110 async fn acquire(
111 &self,
112 _key: &str,
113 _member: &[u8],
114 _ttl_secs: u64,
115 ) -> SmartResult<bool> {
113 async fn acquire(&self, _key: &str, _member: &[u8], _ttl_secs: u64) -> SmartResult<bool> {
116114 Err(SmartError::Internal(NOPUSH_BACKEND_ERROR.to_string()))
117115 }
118116
@@ -262,4 +260,4 @@
262260 let _ = handle.await;
263261 }
264262 }
265}
No newline at end of file
263}
Modified lib/git/src/smart/rpc.rs View lib/git/src/smart/rpc.rs
@@ -64,8 +64,7 @@
6464
6565fn build_command(bare_path: &Path, service: GitService, advertise: bool) -> Command {
6666 let mut cmd = Command::new("git");
67 cmd.arg(service.name())
68 .arg("--stateless-rpc");
67 cmd.arg(service.name()).arg("--stateless-rpc");
6968 if advertise {
7069 cmd.arg("--advertise-refs");
7170 }
@@ -93,9 +92,9 @@
9392 Option<tokio::sync::oneshot::Receiver<SmartResult<()>>>,
9493)> {
9594 let mut cmd = build_command(bare_path, service, advertise);
96 let mut child = cmd
97 .spawn()
98 .map_err(|e| SmartError::Internal(format!("failed to spawn git {}: {e}", service.name())))?;
95 let mut child = cmd.spawn().map_err(|e| {
96 SmartError::Internal(format!("failed to spawn git {}: {e}", service.name()))
97 })?;
9998 let mut stdin = child
10099 .stdin
101100 .take()
@@ -198,7 +197,8 @@
198197 input: Bytes,
199198 timeout_secs: u64,
200199) -> SmartResult<Bytes> {
201 let (mut stream, rx) = spawn_with_supervisor(bare_path, service, advertise, input, timeout_secs)?;
200 let (mut stream, rx) =
201 spawn_with_supervisor(bare_path, service, advertise, input, timeout_secs)?;
202202 let mut out: Vec<u8> = Vec::new();
203203 while let Some(chunk) = stream.next().await {
204204 out.extend_from_slice(&chunk.map_err(|e| {
@@ -209,4 +209,4 @@
209209 let _ = rx.await;
210210 }
211211 Ok(Bytes::from(out))
212}
No newline at end of file
212}
Modified lib/git/src/smart/service.rs View lib/git/src/smart/service.rs
@@ -61,7 +61,9 @@
6161 .context(space.to_string(), repo.to_string())
6262 .await
6363 .map_err(|e| match e {
64 crate::share::errors::GitError::NotFound => SmartError::NotFound(format!("repo {space}/{repo}")),
64 crate::share::errors::GitError::NotFound => {
65 SmartError::NotFound(format!("repo {space}/{repo}"))
66 }
6567 other => SmartError::from(other),
6668 })
6769 }
@@ -86,4 +88,4 @@
8688 }
8789}
8890
89pub use crate::smart::error::SmartError as SmartServiceError;
No newline at end of file
91pub use crate::smart::error::SmartError as SmartServiceError;
Modified lib/git/src/ssh/error.rs View lib/git/src/ssh/error.rs
@@ -37,4 +37,4 @@
3737 SmartError::Redis(e) => Self::Internal(format!("redis error: {e}")),
3838 }
3939 }
40}
No newline at end of file
40}
Modified lib/git/src/ssh/git_service.rs View lib/git/src/ssh/git_service.rs
@@ -59,9 +59,8 @@
5959 return None;
6060 }
6161 for b in path.bytes() {
62 let ok = b.is_ascii_lowercase()
63 || b.is_ascii_digit()
64 || matches!(b, b'.' | b'-' | b'_' | b'/');
62 let ok =
63 b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'.' | b'-' | b'_' | b'/');
6564 if !ok {
6665 return None;
6766 }
@@ -109,4 +108,4 @@
109108 #[cfg(unix)]
110109 cmd.env("GIT_CONFIG_GLOBAL", "/dev/null")
111110 .env("GIT_CONFIG_SYSTEM", "/dev/null");
112}
No newline at end of file
111}
Modified lib/git/src/ssh/mod.rs View lib/git/src/ssh/mod.rs
@@ -6,9 +6,9 @@
66
77pub use error::{SshError, SshResult};
88pub use git_service::{
9 GitService, apply_hardened_env, build_git_advertise_command, parse_git_command, parse_repo_path,
10 sanitize_for_log,
9 GitService, apply_hardened_env, build_git_advertise_command, parse_git_command,
10 parse_repo_path, sanitize_for_log,
1111};
1212pub use handler::GitSshHandler;
1313pub use server::{build_server_config, load_host_key, run_ssh, serve_on_listener};
14pub use service::GitSshService;
No newline at end of file
14pub use service::GitSshService;
Modified lib/git/src/ssh/server.rs View lib/git/src/ssh/server.rs
@@ -85,4 +85,4 @@
8585 let running = server.run_on_socket(config, &listener);
8686 running.await.map_err(SshError::Io)?;
8787 Ok(())
88}
No newline at end of file
88}
Modified lib/git/src/ssh/service.rs View lib/git/src/ssh/service.rs
@@ -32,4 +32,4 @@
3232 };
3333 PushCoordinator::new(backend)
3434 }
35}
No newline at end of file
35}
Modified lib/git/src/sync/branches.rs View lib/git/src/sync/branches.rs
@@ -137,9 +137,7 @@
137137 baby.get_default_branch().await,
138138 Ok(current) if current == desired
139139 );
140 if !head_ok
141 && let Err(e) = baby.set_default_branch(&desired).await
142 {
140 if !head_ok && let Err(e) = baby.set_default_branch(&desired).await {
143141 tracing::warn!(
144142 repo_id = %repo.id,
145143 branch = %desired,
Modified lib/git/src/sync/fsck.rs View lib/git/src/sync/fsck.rs
@@ -47,4 +47,4 @@
4747 tracing::warn!(repo_dir = %dir_str, error = %e, "fsck failed");
4848 }
4949 }
50}
No newline at end of file
50}
Modified lib/git/src/sync/languages.rs View lib/git/src/sync/languages.rs
@@ -110,11 +110,7 @@
110110 return Ok(None);
111111 };
112112 let timeout = Duration::from_secs(settings.language_timeout_secs);
113 let stats = tokio::time::timeout(
114 timeout,
115 walk_lang_stats(baby.clone(), revision),
116 )
117 .await;
113 let stats = tokio::time::timeout(timeout, walk_lang_stats(baby.clone(), revision)).await;
118114
119115 let entries: Vec<(String, i64)> = match stats {
120116 Ok(Ok(map)) => map.into_iter().collect(),
@@ -149,10 +145,7 @@
149145 Ok(Some(out))
150146}
151147
152async fn resolve_language_revision(
153 baby: &GitBaby,
154 repo: &repos::Model,
155) -> Option<String> {
148async fn resolve_language_revision(baby: &GitBaby, repo: &repos::Model) -> Option<String> {
156149 let mut candidates: Vec<String> = Vec::new();
157150 candidates.push("HEAD".to_string());
158151 let db = repo.default_branch.trim();
Modified lib/git/src/sync/license.rs View lib/git/src/sync/license.rs
@@ -6,7 +6,13 @@
66
77use crate::sync::error::{SyncError, SyncResult};
88
9const LICENSE_FILES: &[&str] = &["LICENSE", "LICENSE.md", "LICENSE.txt", "COPYING", "COPYING.md"];
9const LICENSE_FILES: &[&str] = &[
10 "LICENSE",
11 "LICENSE.md",
12 "LICENSE.txt",
13 "COPYING",
14 "COPYING.md",
15];
1016
1117pub async fn sync_repo_license(
1218 txn: &DatabaseTransaction,
@@ -150,4 +156,4 @@
150156 _ => return None,
151157 };
152158 Some(format!("https://spdx.org/licenses/{slug}.html"))
153}
No newline at end of file
159}
Modified lib/git/src/sync/sync.rs View lib/git/src/sync/sync.rs
@@ -149,7 +149,7 @@
149149 languages::sync_repo_languages(&baby, &txn, &self.repo, &self.settings).await?;
150150 tracing::debug!(repo_id = %self.repo.id, "languages synced");
151151
152stats::sync_repo_stats(
152 stats::sync_repo_stats(
153153 &txn,
154154 &self.repo,
155155 &self.repo_dir(),
Modified lib/git/src/errors.rs View lib/git/src/errors.rs
@@ -1,9 +1,9 @@
11use std::fmt;
22
3use axum::Json;
34use axum::extract::rejection::{PathRejection, QueryRejection};
45use axum::http::StatusCode;
56use axum::response::{IntoResponse, Response};
6use axum::Json;
77use export::rest::{AppResponseData, NoData};
88use utoipa::IntoResponses;
99
@@ -132,7 +132,8 @@
132132impl IntoResponse for ApiError {
133133 fn into_response(self) -> Response {
134134 let (code, envelope) = self.decompose();
135 let status = StatusCode::from_u16(code).unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR);
135 let status =
136 StatusCode::from_u16(code).unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR);
136137 (status, Json(envelope)).into_response()
137138 }
138139}
@@ -163,4 +164,4 @@
163164 fn from(_error: QueryRejection) -> Self {
164165 Self::BadRequest(Self::payload(400, "bad request"))
165166 }
166}
No newline at end of file
167}
Modified lib/git/src/openapi.rs View lib/git/src/openapi.rs
@@ -252,4 +252,4 @@
252252 crate::handler::tags::verify::TagVerifyDto,
253253 ))
254254)]
255pub struct GitApiDoc;
No newline at end of file
255pub struct GitApiDoc;
Modified lib/git/src/routes.rs View lib/git/src/routes.rs
@@ -131,8 +131,11 @@
131131 get(repo_get).patch(repo_update).delete(repo_archive),
132132 )
133133 .route("/{space_name}/{repo_name}/archive", post(repo_archive))
134 .route("/{space_name}/{repo_name}/branches", get(branch_list).post(branch_create))
135134 .route(
135 "/{space_name}/{repo_name}/branches",
136 get(branch_list).post(branch_create),
137 )
138 .route(
136139 "/{space_name}/{repo_name}/branches/{name}",
137140 get(branch_get).delete(branch_delete).patch(branch_rename),
138141 )
@@ -140,8 +143,11 @@
140143 "/{space_name}/{repo_name}/branches/{name}/default",
141144 post(branch_set_default),
142145 )
143 .route("/{space_name}/{repo_name}/tags", get(tag_list).post(tag_create))
144146 .route(
147 "/{space_name}/{repo_name}/tags",
148 get(tag_list).post(tag_create),
149 )
150 .route(
145151 "/{space_name}/{repo_name}/tags/{name}",
146152 get(tag_get).delete(tag_delete),
147153 )
@@ -167,20 +173,14 @@
167173 .route(
168174 "/{space_name}/{repo_name}/refs/{name}/resolve",
169175 get(refs_resolve),
170 )
171 .route(
172 "/{space_name}/{repo_name}/commits",
173 get(list_commits),
174176 )
177 .route("/{space_name}/{repo_name}/commits", get(list_commits))
175178 .route(
176179 "/{space_name}/{repo_name}/commits/{revision}",
177180 get(get_commit),
178181 )
182 .route("/{space_name}/{repo_name}/commits/stats", get(commit_stats))
179183 .route(
180 "/{space_name}/{repo_name}/commits/stats",
181 get(commit_stats),
182 )
183 .route(
184184 "/{space_name}/{repo_name}/commits/{revision}/tree",
185185 get(commit_tree_entries),
186186 )
@@ -192,11 +192,8 @@
192192 "/{space_name}/{repo_name}/commits/{revision}/sync",
193193 post(sync_commit),
194194 )
195 .route("/{space_name}/{repo_name}/rev/{revision}", get(rev_parse))
195196 .route(
196 "/{space_name}/{repo_name}/rev/{revision}",
197 get(rev_parse),
198 )
199 .route(
200197 "/{space_name}/{repo_name}/commit-statuses",
201198 post(set_commit_status),
202199 )
@@ -211,10 +208,7 @@
211208 )
212209 .route("/{space_name}/{repo_name}/logs/last", get(log_last))
213210 .route("/{space_name}/{repo_name}/logs/graph", get(log_graph))
214 .route(
215 "/{space_name}/{repo_name}/path-log",
216 get(path_log),
217 )
211 .route("/{space_name}/{repo_name}/path-log", get(path_log))
218212 .route(
219213 "/{space_name}/{repo_name}/blobs/{oid}/metadata",
220214 get(blob_metadata),
@@ -222,20 +216,17 @@
222216 .route(
223217 "/{space_name}/{repo_name}/blobs/{oid}/content",
224218 get(blob_content),
225 )
226 .route(
227 "/{space_name}/{repo_name}/blobs/{oid}/raw",
228 get(blob_raw),
229219 )
220 .route("/{space_name}/{repo_name}/blobs/{oid}/raw", get(blob_raw))
230221 .route(
231222 "/{space_name}/{repo_name}/lfs-pointers",
232223 get(list_lfs_pointers),
233224 )
225 .route("/{space_name}/{repo_name}/tree/{revision}", get(tree_read))
234226 .route(
235 "/{space_name}/{repo_name}/tree/{revision}",
236 get(tree_read),
227 "/{space_name}/{repo_name}/tree/resolve",
228 get(tree_resolve_path),
237229 )
238 .route("/{space_name}/{repo_name}/tree/resolve", get(tree_resolve_path))
239230 .route("/{space_name}/{repo_name}/tree/walk", get(tree_walk))
240231 .route("/{space_name}/{repo_name}/tree/follow", get(tree_follow))
241232 .route(
@@ -243,10 +234,7 @@
243234 get(tree_last_updated),
244235 )
245236 .route("/{space_name}/{repo_name}/blame", get(blame_handler))
246 .route(
247 "/{space_name}/{repo_name}/submodules",
248 get(submodules_list),
249 )
237 .route("/{space_name}/{repo_name}/submodules", get(submodules_list))
250238 .route(
251239 "/{space_name}/{repo_name}/submodules/resolve",
252240 get(submodules_resolve),
@@ -259,10 +247,7 @@
259247 "/{space_name}/{repo_name}/signatures/tag/{name}",
260248 get(verify_tag),
261249 )
262 .route(
263 "/{space_name}/{repo_name}/diff/raw",
264 get(diff_raw),
265 )
250 .route("/{space_name}/{repo_name}/diff/raw", get(diff_raw))
266251 .route(
267252 "/{space_name}/{repo_name}/diff/files",
268253 get(files_changed_between),
@@ -271,30 +256,18 @@
271256 "/{space_name}/{repo_name}/diff/shortstat",
272257 get(diff_short_stat),
273258 )
259 .route("/{space_name}/{repo_name}/diff/stats", get(diff_stats))
274260 .route(
275 "/{space_name}/{repo_name}/diff/stats",
276 get(diff_stats),
277 )
278 .route(
279261 "/{space_name}/{repo_name}/diff/for-commit/{commit}",
280262 get(diff_for_commit),
281263 )
282 .route(
283 "/{space_name}/{repo_name}/diff/tree",
284 get(diff_tree),
285 )
286 .route(
287 "/{space_name}/{repo_name}/diff/range",
288 post(range_diff),
289 )
264 .route("/{space_name}/{repo_name}/diff/tree", get(diff_tree))
265 .route("/{space_name}/{repo_name}/diff/range", post(range_diff))
290266 .route(
291267 "/{space_name}/{repo_name}/patch/generate",
292268 get(patch_generate),
293269 )
294 .route(
295 "/{space_name}/{repo_name}/patch/apply",
296 post(patch_apply),
297 )
270 .route("/{space_name}/{repo_name}/patch/apply", post(patch_apply))
298271 .route("/_/patch/parse", post(patch_parse))
299272 .route(
300273 "/{space_name}/{repo_name}/conflicts/check",
@@ -320,10 +293,7 @@
320293 "/{space_name}/{repo_name}/remotes/fetch",
321294 post(remote_fetch),
322295 )
323 .route(
324 "/{space_name}/{repo_name}/remotes/push",
325 post(remote_push),
326 )
296 .route("/{space_name}/{repo_name}/remotes/push", post(remote_push))
327297 .route(
328298 "/{space_name}/{repo_name}/remotes/{name}",
329299 post(remote_add).delete(remote_remove),
@@ -348,47 +318,20 @@
348318 "/{space_name}/{repo_name}/hooks/{script}",
349319 post(execute_script),
350320 )
321 .route("/{space_name}/{repo_name}/pipelines/trigger", post(trigger))
351322 .route(
352 "/{space_name}/{repo_name}/pipelines/trigger",
353 post(trigger),
354 )
355 .route(
356323 "/{space_name}/{repo_name}/pipelines/schedule",
357324 post(schedule),
358325 )
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))
332 .route("/_/contents/signature", post(caller_signature))
333 .route("/{space_name}/{repo_name}/contents/blobs", post(write_blob))
359334 .route(
360 "/_/pipelines/cancel",
361 post(cancel),
362 )
363 .route(
364 "/{space_name}/{repo_name}/packs/stats",
365 get(pack_stats),
366 )
367 .route(
368 "/{space_name}/{repo_name}/packs/ingest",
369 post(pack_ingest),
370 )
371 .route(
372 "/{space_name}/{repo_name}/packs/repack",
373 post(pack_repack),
374 )
375 .route(
376 "/{space_name}/{repo_name}/maintenance/prune",
377 post(prune),
378 )
379 .route(
380 "/{space_name}/{repo_name}/maintenance/fsck",
381 post(fsck),
382 )
383 .route(
384 "/_/contents/signature",
385 post(caller_signature),
386 )
387 .route(
388 "/{space_name}/{repo_name}/contents/blobs",
389 post(write_blob),
390 )
391 .route(
392335 "/{space_name}/{repo_name}/contents/trees",
393336 post(write_tree_from_files),
394337 )
@@ -424,22 +367,13 @@
424367 "/{space_name}/{repo_name}/star",
425368 put(star_repo).delete(unstar_repo).get(is_starred),
426369 )
427 .route(
428 "/{space_name}/{repo_name}/stargazers",
429 get(list_stargazers),
430 )
370 .route("/{space_name}/{repo_name}/stargazers", get(list_stargazers))
431371 .route(
432372 "/{space_name}/{repo_name}/watch",
433373 put(upsert_watch).delete(unwatch).get(get_watch_mode),
434 )
435 .route(
436 "/{space_name}/{repo_name}/watchers",
437 get(list_watchers),
438374 )
439 .route(
440 "/{space_name}/{repo_name}/topics",
441 get(list_repo_topics),
442 )
375 .route("/{space_name}/{repo_name}/watchers", get(list_watchers))
376 .route("/{space_name}/{repo_name}/topics", get(list_repo_topics))
443377 .route(
444378 "/{space_name}/{repo_name}/topics/{topic_name}",
445379 put(add_topic_to_repo).delete(remove_topic_from_repo),
@@ -477,4 +411,4 @@
477411 Router::new()
478412 .route("/", get(list_licenses))
479413 .route("/{name}", get(get_license).put(upsert_license))
480}
No newline at end of file
414}
Modified lib/issues/src/services/boards.rs View lib/issues/src/services/boards.rs
@@ -148,7 +148,9 @@
148148
149149 let name = cmd.name.trim();
150150 if name.is_empty() || name.chars().count() > self.config().issues.max_board_name_len {
151 return Err(IssueError::BadRequest("board name is empty or too long".to_string()));
151 return Err(IssueError::BadRequest(
152 "board name is empty or too long".to_string(),
153 ));
152154 }
153155
154156 if let Some(p) = cmd.parent_dir_id {
@@ -159,7 +161,9 @@
159161 .ok_or(IssueError::NotFound)?;
160162
161163 if parent.kind != DirKind::Dir {
162 return Err(IssueError::BadRequest("parent node must be a dir".to_string()));
164 return Err(IssueError::BadRequest(
165 "parent node must be a dir".to_string(),
166 ));
163167 }
164168 }
165169
@@ -234,7 +238,10 @@
234238
235239 let max_pagination = self.config().issues.max_pagination;
236240 let default_pagination = self.config().issues.default_pagination;
237 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
241 let limit = q
242 .limit
243 .unwrap_or(default_pagination)
244 .clamp(1, max_pagination);
238245 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
239246
240247 let dirs_in_space = dirs::Entity::find()
@@ -267,8 +274,7 @@
267274 });
268275 }
269276
270 let mut select = issues::Entity::find()
271 .filter(issues::Column::DirId.is_in(dir_ids));
277 let mut select = issues::Entity::find().filter(issues::Column::DirId.is_in(dir_ids));
272278
273279 if let Some(ref search) = q.q {
274280 let kw = search.trim();
@@ -336,7 +342,9 @@
336342 if let Some(new_name) = cmd.name {
337343 let name = new_name.trim();
338344 if name.is_empty() || name.chars().count() > self.config().issues.max_board_name_len {
339 return Err(IssueError::BadRequest("board name is empty or too long".to_string()));
345 return Err(IssueError::BadRequest(
346 "board name is empty or too long".to_string(),
347 ));
340348 }
341349 am.name = Set(name.to_string());
342350 dir_am.name = Set(name.to_string());
Modified lib/issues/src/services/comments.rs View lib/issues/src/services/comments.rs
@@ -78,7 +78,9 @@
7878
7979 let content = cmd.content.trim();
8080 if content.is_empty() || content.chars().count() > self.config().issues.max_comment_len {
81 return Err(IssueError::BadRequest("comment content is empty or too long".to_string()));
81 return Err(IssueError::BadRequest(
82 "comment content is empty or too long".to_string(),
83 ));
8284 }
8385
8486 let mut parent_poster_id: Option<Uuid> = None;
@@ -98,9 +100,7 @@
98100 }
99101 }
100102
101 let mentioned_uids = self
102 .collect_mentioned_uids(&space, content, caller)
103 .await?;
103 let mentioned_uids = self.collect_mentioned_uids(&space, content, caller).await?;
104104
105105 let now = OffsetDateTime::now_utc();
106106 let comment_id = Uuid::now_v7();
@@ -199,7 +199,9 @@
199199 txn,
200200 m_uid,
201201 NotificationType::Mention,
202 format!("You were mentioned in issue #{issue_num}: {issue_title_clone}"),
202 format!(
203 "You were mentioned in issue #{issue_num}: {issue_title_clone}"
204 ),
203205 Some(content_clone.clone()),
204206 Some(data),
205207 Some(caller),
@@ -284,11 +286,14 @@
284286
285287 let max_pagination = self.config().issues.max_pagination;
286288 let default_pagination = self.config().issues.default_pagination;
287 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
289 let limit = q
290 .limit
291 .unwrap_or(default_pagination)
292 .clamp(1, max_pagination);
288293 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
289294
290 let mut select = comment::Entity::find()
291 .filter(comment::Column::IssueId.eq(Some(issue_id)));
295 let mut select =
296 comment::Entity::find().filter(comment::Column::IssueId.eq(Some(issue_id)));
292297
293298 if let Some(pid) = q.parent_id {
294299 select = select.filter(comment::Column::ParentId.eq(Some(pid)));
@@ -384,7 +389,9 @@
384389
385390 let content = cmd.content.trim();
386391 if content.is_empty() || content.chars().count() > self.config().issues.max_comment_len {
387 return Err(IssueError::BadRequest("comment content is empty or too long".to_string()));
392 return Err(IssueError::BadRequest(
393 "comment content is empty or too long".to_string(),
394 ));
388395 }
389396
390397 let old_content = c.content.clone();
Modified lib/issues/src/services/content_history.rs View lib/issues/src/services/content_history.rs
@@ -1,7 +1,5 @@
11use models::issue::issue_content_history;
2use sea_orm::{
3 ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect,
4};
2use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
53use serde::{Deserialize, Serialize};
64use space::services::spaces::UserBriefDto;
75use time::OffsetDateTime;
@@ -60,7 +58,10 @@
6058
6159 let max_pagination = self.config().issues.max_pagination;
6260 let default_pagination = self.config().issues.default_pagination;
63 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
61 let limit = q
62 .limit
63 .unwrap_or(default_pagination)
64 .clamp(1, max_pagination);
6465 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
6566
6667 let mut select = issue_content_history::Entity::find()
Modified lib/issues/src/services/dependencies.rs View lib/issues/src/services/dependencies.rs
@@ -1,7 +1,6 @@
11use models::issue::issue_dependency;
22use sea_orm::{
3 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set,
4 TransactionTrait,
3 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set, TransactionTrait,
54};
65use serde::{Deserialize, Serialize};
76use time::OffsetDateTime;
@@ -61,7 +60,9 @@
6160 let _ = lookup_issue(self, &board, issue_id).await?;
6261
6362 if cmd.dependency_id == issue_id {
64 return Err(IssueError::BadRequest("issue cannot depend on itself".to_string()));
63 return Err(IssueError::BadRequest(
64 "issue cannot depend on itself".to_string(),
65 ));
6566 }
6667
6768 let _ = lookup_issue(self, &board, cmd.dependency_id).await?;
@@ -73,7 +74,9 @@
7374 .await?;
7475
7576 if existing.is_some() {
76 return Err(IssueError::Conflict("dependency already exists".to_string()));
77 return Err(IssueError::Conflict(
78 "dependency already exists".to_string(),
79 ));
7780 }
7881
7982 let now = OffsetDateTime::now_utc();
Modified lib/issues/src/services/events.rs View lib/issues/src/services/events.rs
@@ -1,7 +1,5 @@
11use models::issue::issue_event_line;
2use sea_orm::{
3 ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect,
4};
2use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
53use serde::{Deserialize, Serialize};
64use space::services::spaces::UserBriefDto;
75use time::OffsetDateTime;
@@ -63,7 +61,10 @@
6361
6462 let max_pagination = self.config().issues.max_pagination;
6563 let default_pagination = self.config().issues.default_pagination;
66 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
64 let limit = q
65 .limit
66 .unwrap_or(default_pagination)
67 .clamp(1, max_pagination);
6768 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
6869
6970 let select = issue_event_line::Entity::find()
Modified lib/issues/src/services/issue_users.rs View lib/issues/src/services/issue_users.rs
@@ -65,7 +65,10 @@
6565
6666 let max_pagination = self.config().issues.max_pagination;
6767 let default_pagination = self.config().issues.default_pagination;
68 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
68 let limit = q
69 .limit
70 .unwrap_or(default_pagination)
71 .clamp(1, max_pagination);
6972 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
7073
7174 let select = issue_user::Entity::find()
Modified lib/issues/src/services/issues.rs View lib/issues/src/services/issues.rs
@@ -210,7 +210,9 @@
210210
211211 let title = cmd.title.trim();
212212 if title.is_empty() || title.chars().count() > self.config().issues.max_title_len {
213 return Err(IssueError::BadRequest("issue title is empty or too long".to_string()));
213 return Err(IssueError::BadRequest(
214 "issue title is empty or too long".to_string(),
215 ));
214216 }
215217 if cmd.body.chars().count() > self.config().issues.max_body_len {
216218 return Err(IssueError::BadRequest("issue body is too long".to_string()));
@@ -303,9 +305,7 @@
303305 id: Set(Uuid::now_v7()),
304306 issue_id: Set(issue_id),
305307 user_id: Set(m_uid),
306 kind: Set(
307 models::issue::issue_member::IssueMemberKind::Mentioned,
308 ),
308 kind: Set(models::issue::issue_member::IssueMemberKind::Mentioned),
309309 created_at: Set(now),
310310 updated_at: Set(None),
311311 };
@@ -392,11 +392,14 @@
392392
393393 let max_pagination = self.config().issues.max_pagination;
394394 let default_pagination = self.config().issues.default_pagination;
395 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
395 let limit = q
396 .limit
397 .unwrap_or(default_pagination)
398 .clamp(1, max_pagination);
396399 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
397400
398 let mut select = issue_item::Entity::find()
399 .filter(issue_item::Column::CollectionId.eq(board.id));
401 let mut select =
402 issue_item::Entity::find().filter(issue_item::Column::CollectionId.eq(board.id));
400403
401404 if let Some(state) = q.state {
402405 match state {
@@ -527,14 +530,20 @@
527530 if let Some(new_title) = cmd.title {
528531 let t = new_title.trim();
529532 if t.is_empty() || t.chars().count() > self.config().issues.max_title_len {
530 return Err(IssueError::BadRequest("issue title is empty or too long".to_string()));
533 return Err(IssueError::BadRequest(
534 "issue title is empty or too long".to_string(),
535 ));
531536 }
532537 if t != old_title {
533538 title_changed = Some(t.to_string());
534539 }
535540 }
536541
537 if cmd.body.as_ref().is_some_and(|b| b.chars().count() > self.config().issues.max_body_len) {
542 if cmd
543 .body
544 .as_ref()
545 .is_some_and(|b| b.chars().count() > self.config().issues.max_body_len)
546 {
538547 return Err(IssueError::BadRequest("issue body is too long".to_string()));
539548 }
540549
@@ -791,17 +800,8 @@
791800 .transaction::<_, issue_item::Model, IssueError>(move |txn| {
792801 Box::pin(async move {
793802 let u = am.update(txn).await?;
794 this.write_event(
795 txn,
796 issue_id,
797 caller,
798 event_type,
799 None,
800 None,
801 None,
802 None,
803 )
804 .await?;
803 this.write_event(txn, issue_id, caller, event_type, None, None, None, None)
804 .await?;
805805 Ok(u)
806806 })
807807 })
Modified lib/issues/src/services/labels.rs View lib/issues/src/services/labels.rs
@@ -125,7 +125,9 @@
125125
126126 let name = cmd.name.trim();
127127 if name.is_empty() || name.chars().count() > self.config().issues.max_label_name_len {
128 return Err(IssueError::BadRequest("label name is empty or too long".to_string()));
128 return Err(IssueError::BadRequest(
129 "label name is empty or too long".to_string(),
130 ));
129131 }
130132
131133 let color = cmd.color.trim();
@@ -174,11 +176,14 @@
174176
175177 let max_pagination = self.config().issues.max_pagination;
176178 let default_pagination = self.config().issues.default_pagination;
177 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
179 let limit = q
180 .limit
181 .unwrap_or(default_pagination)
182 .clamp(1, max_pagination);
178183 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
179184
180 let mut select = issue_label::Entity::find()
181 .filter(issue_label::Column::DirId.eq(board.dir_id));
185 let mut select =
186 issue_label::Entity::find().filter(issue_label::Column::DirId.eq(board.dir_id));
182187
183188 if let Some(archived) = q.is_archived {
184189 select = select.filter(issue_label::Column::IsArchived.eq(archived));
@@ -247,7 +252,9 @@
247252 if let Some(new_name) = cmd.name {
248253 let name = new_name.trim();
249254 if name.is_empty() || name.chars().count() > self.config().issues.max_label_name_len {
250 return Err(IssueError::BadRequest("label name is empty or too long".to_string()));
255 return Err(IssueError::BadRequest(
256 "label name is empty or too long".to_string(),
257 ));
251258 }
252259 am.name = Set(name.to_string());
253260 }
@@ -312,7 +319,9 @@
312319 .filter(issue_relation_label::Column::LabelId.eq(label_id))
313320 .exec(txn)
314321 .await?;
315 issue_label::Entity::delete_by_id(label_id).exec(txn).await?;
322 issue_label::Entity::delete_by_id(label_id)
323 .exec(txn)
324 .await?;
316325 Ok(())
317326 })
318327 })
@@ -378,7 +387,9 @@
378387 .await?;
379388
380389 if existing.is_some() {
381 return Err(IssueError::Conflict("label already bound to issue".to_string()));
390 return Err(IssueError::Conflict(
391 "label already bound to issue".to_string(),
392 ));
382393 }
383394
384395 let now = OffsetDateTime::now_utc();
Modified lib/issues/src/services/members.rs View lib/issues/src/services/members.rs
@@ -208,11 +208,14 @@
208208
209209 let max_pagination = self.config().issues.max_pagination;
210210 let default_pagination = self.config().issues.default_pagination;
211 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
211 let limit = q
212 .limit
213 .unwrap_or(default_pagination)
214 .clamp(1, max_pagination);
212215 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
213216
214 let mut select = issue_member::Entity::find()
215 .filter(issue_member::Column::IssueId.eq(issue_id));
217 let mut select =
218 issue_member::Entity::find().filter(issue_member::Column::IssueId.eq(issue_id));
216219
217220 if let Some(kind) = q.kind {
218221 select = select.filter(issue_member::Column::Kind.eq(kind));
Modified lib/issues/src/services/milestones.rs View lib/issues/src/services/milestones.rs
@@ -74,7 +74,11 @@
7474}
7575
7676impl IssueService {
77 fn milestone_dto(m: milestone::Model, nums_issues: i64, nums_closed_issues: i64) -> MilestoneDto {
77 fn milestone_dto(
78 m: milestone::Model,
79 nums_issues: i64,
80 nums_closed_issues: i64,
81 ) -> MilestoneDto {
7882 MilestoneDto {
7983 id: m.id,
8084 board_id: m.collection_id,
@@ -134,10 +138,7 @@
134138 Ok((total_counts, closed_counts))
135139 }
136140
137 async fn build_milestone_dto(
138 &self,
139 m: milestone::Model,
140 ) -> Result<MilestoneDto, IssueError> {
141 async fn build_milestone_dto(&self, m: milestone::Model) -> Result<MilestoneDto, IssueError> {
141142 let (total_counts, closed_counts) = self.issue_counts_by_milestone(&[m.id]).await?;
142143 let nums_issues = total_counts.get(&m.id).copied().unwrap_or(0);
143144 let nums_closed_issues = closed_counts.get(&m.id).copied().unwrap_or(0);
@@ -158,10 +159,16 @@
158159
159160 let name = cmd.name.trim();
160161 if name.is_empty() || name.chars().count() > self.config().issues.max_milestone_title_len {
161 return Err(IssueError::BadRequest("milestone name is empty or too long".to_string()));
162 return Err(IssueError::BadRequest(
163 "milestone name is empty or too long".to_string(),
164 ));
162165 }
163166
164 if cmd.description.as_ref().is_some_and(|d| d.chars().count() > self.config().issues.max_milestone_description_len) {
167 if cmd
168 .description
169 .as_ref()
170 .is_some_and(|d| d.chars().count() > self.config().issues.max_milestone_description_len)
171 {
165172 return Err(IssueError::BadRequest(
166173 "milestone description is too long".to_string(),
167174 ));
@@ -209,11 +216,14 @@
209216
210217 let max_pagination = self.config().issues.max_pagination;
211218 let default_pagination = self.config().issues.default_pagination;
212 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
219 let limit = q
220 .limit
221 .unwrap_or(default_pagination)
222 .clamp(1, max_pagination);
213223 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
214224
215 let mut select = milestone::Entity::find()
216 .filter(milestone::Column::CollectionId.eq(board.id));
225 let mut select =
226 milestone::Entity::find().filter(milestone::Column::CollectionId.eq(board.id));
217227
218228 if let Some(closed) = q.is_closed {
219229 select = select.filter(milestone::Column::IsClosed.eq(closed));
@@ -236,9 +246,7 @@
236246 .await?;
237247
238248 let milestone_ids: Vec<Uuid> = rows.iter().map(|m| m.id).collect();
239 let (total_counts, closed_counts) = self
240 .issue_counts_by_milestone(&milestone_ids)
241 .await?;
249 let (total_counts, closed_counts) = self.issue_counts_by_milestone(&milestone_ids).await?;
242250
243251 let items = rows
244252 .into_iter()
@@ -306,7 +314,9 @@
306314 }
307315
308316 if let Some(desc) = cmd.description {
309 if desc.as_ref().is_some_and(|d| d.chars().count() > self.config().issues.max_milestone_description_len) {
317 if desc.as_ref().is_some_and(|d| {
318 d.chars().count() > self.config().issues.max_milestone_description_len
319 }) {
310320 return Err(IssueError::BadRequest(
311321 "milestone description is too long".to_string(),
312322 ));
@@ -356,7 +366,9 @@
356366 .exec(txn)
357367 .await?;
358368
359 milestone::Entity::delete_by_id(milestone_id).exec(txn).await?;
369 milestone::Entity::delete_by_id(milestone_id)
370 .exec(txn)
371 .await?;
360372 Ok(())
361373 })
362374 })
Modified lib/issues/src/services/pins.rs View lib/issues/src/services/pins.rs
@@ -1,7 +1,5 @@
11use models::issue::issue_pin;
2use sea_orm::{
3 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set,
4};
2use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
53use serde::{Deserialize, Serialize};
64use time::OffsetDateTime;
75use utoipa::{IntoParams, ToSchema};
Modified lib/issues/src/services/reactions.rs View lib/issues/src/services/reactions.rs
@@ -3,9 +3,7 @@
33use models::issue::comment;
44use models::issue::issue_reactions;
55use models::space::space_join_rule::SpaceJoinTrigger;
6use sea_orm::{
7 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set,
8};
6use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
97use serde::{Deserialize, Serialize};
108use space::services::join_rules::apply_space_join_rules;
119use space::services::spaces::UserBriefDto;
@@ -66,7 +64,9 @@
6664 fn validate_emoji(&self, emoji: &str) -> Result<String, IssueError> {
6765 let clean = emoji.trim();
6866 if clean.is_empty() || clean.chars().count() > self.config().issues.max_reaction_emoji_len {
69 return Err(IssueError::BadRequest("emoji is empty or too long".to_string()));
67 return Err(IssueError::BadRequest(
68 "emoji is empty or too long".to_string(),
69 ));
7070 }
7171 Ok(clean.to_string())
7272 }
Modified lib/issues/src/services/repo_relations.rs View lib/issues/src/services/repo_relations.rs
@@ -1,8 +1,6 @@
11use models::issue::issue_relation_repo;
22use models::repo::repos;
3use sea_orm::{
4 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set,
5};
3use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
64use serde::{Deserialize, Serialize};
75use time::OffsetDateTime;
86use utoipa::{IntoParams, ToSchema};
@@ -72,7 +70,9 @@
7270 .await?;
7371
7472 if existing.is_some() {
75 return Err(IssueError::Conflict("repo relation already exists".to_string()));
73 return Err(IssueError::Conflict(
74 "repo relation already exists".to_string(),
75 ));
7676 }
7777
7878 let now = OffsetDateTime::now_utc();
Modified lib/issues/src/services/service.rs View lib/issues/src/services/service.rs
@@ -3,9 +3,9 @@
33
44use async_trait::async_trait;
55use config::AppConfig;
6use export::database::AppDatabase;
76use email::EmailService;
87use export::AppState;
8use export::database::AppDatabase;
99use models::issue::issue_event_line;
1010use models::space::space_members::{self, MemberStatus};
1111use models::space::spaces;
@@ -13,11 +13,9 @@
1313use models::users::user_emails;
1414use models::users::user_notifications::{self, NotificationType};
1515use models::users::user_sessions;
16use sea_orm::{
17 ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, Set,
18};
19use session::share::guard::{SessionGuard, SessionWithUser};
16use sea_orm::{ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, Set};
2017use session::SessionError;
18use session::share::guard::{SessionGuard, SessionWithUser};
2119use space::services::spaces::UserBriefDto;
2220use time::OffsetDateTime;
2321use uuid::Uuid;
@@ -204,7 +202,10 @@
204202 .all(self.db().read())
205203 .await?;
206204
207 let map = rows.into_iter().map(|r| (r.user_id, r.lower_email)).collect();
205 let map = rows
206 .into_iter()
207 .map(|r| (r.user_id, r.lower_email))
208 .collect();
208209 Ok(map)
209210 }
210211
Modified lib/issues/src/services/stopwatch.rs View lib/issues/src/services/stopwatch.rs
@@ -1,8 +1,6 @@
11use models::issue::stopwatch;
22use models::issue::tracked_time;
3use sea_orm::{
4 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set, TransactionTrait,
5};
3use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set, TransactionTrait};
64use serde::{Deserialize, Serialize};
75use space::services::spaces::UserBriefDto;
86use time::OffsetDateTime;
Modified lib/issues/src/services/tracked_time.rs View lib/issues/src/services/tracked_time.rs
@@ -1,7 +1,5 @@
11use models::issue::tracked_time;
2use sea_orm::{
3 ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect,
4};
2use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
53use serde::{Deserialize, Serialize};
64use space::services::spaces::UserBriefDto;
75use time::OffsetDateTime;
@@ -57,11 +55,14 @@
5755
5856 let max_pagination = self.config().issues.max_pagination;
5957 let default_pagination = self.config().issues.default_pagination;
60 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
58 let limit = q
59 .limit
60 .unwrap_or(default_pagination)
61 .clamp(1, max_pagination);
6162 let offset = q.offset.unwrap_or(0).clamp(0, 5000);
6263
63 let mut select = tracked_time::Entity::find()
64 .filter(tracked_time::Column::IssueId.eq(issue_id));
64 let mut select =
65 tracked_time::Entity::find().filter(tracked_time::Column::IssueId.eq(issue_id));
6566
6667 if let Some(uid) = q.user_id {
6768 select = select.filter(tracked_time::Column::UserId.eq(uid));
Modified lib/issues/src/services/watches.rs View lib/issues/src/services/watches.rs
@@ -1,8 +1,6 @@
11use models::common::watch_mode::WatchMode;
22use models::issue::issue_watch;
3use sea_orm::{
4 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set,
5};
3use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
64use serde::{Deserialize, Serialize};
75use space::services::spaces::UserBriefDto;
86use time::OffsetDateTime;
Modified lib/issues/src/share/authz.rs View lib/issues/src/share/authz.rs
@@ -1,5 +1,5 @@
11use models::space::spaces;
2use space::share::authz::{effective_permissions, MANAGE_CHANNELS};
2use space::share::authz::{MANAGE_CHANNELS, effective_permissions};
33use uuid::Uuid;
44
55use crate::errors::IssueError;
Modified lib/issues/src/share/lookup.rs View lib/issues/src/share/lookup.rs
@@ -3,7 +3,7 @@
33use models::issue::issues;
44use models::space::spaces;
55use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
6use space::share::authz::{effective_permissions, READ, VIEW_SPACE};
6use space::share::authz::{READ, VIEW_SPACE, effective_permissions};
77use uuid::Uuid;
88
99use crate::errors::IssueError;
Modified lib/issues/src/share/mod.rs View lib/issues/src/share/mod.rs
@@ -1,3 +1,3 @@
11pub mod authz;
22pub mod guard;
3pub mod lookup;
No newline at end of file
3pub mod lookup;
Modified lib/issues/src/errors.rs View lib/issues/src/errors.rs
@@ -1,9 +1,9 @@
11use std::fmt;
22
3use axum::Json;
34use axum::extract::rejection::{PathRejection, QueryRejection};
45use axum::http::StatusCode;
56use axum::response::{IntoResponse, Response};
6use axum::Json;
77use export::rest::{AppResponseData, NoData};
88use utoipa::IntoResponses;
99
Modified lib/issues/src/routes.rs View lib/issues/src/routes.rs
@@ -1,5 +1,5 @@
1use axum::Router;
12use axum::routing::{delete, get, patch, post};
2use axum::Router;
33
44use crate::handlers::boards::create_board::create_board;
55use crate::handlers::boards::delete_board::delete_board;
Modified lib/models/src/dir/dirs.rs View lib/models/src/dir/dirs.rs
@@ -4,7 +4,16 @@
44use uuid::Uuid;
55
66#[derive(
7 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 Default,
13 EnumIter,
14 DeriveActiveEnum,
15 Serialize,
16 Deserialize,
817 utoipa::ToSchema,
918)]
1019#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
Modified lib/models/src/git/git_commit_status.rs View lib/models/src/git/git_commit_status.rs
@@ -4,7 +4,16 @@
44use uuid::Uuid;
55
66#[derive(
7 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 Default,
13 EnumIter,
14 DeriveActiveEnum,
15 Serialize,
16 Deserialize,
817 utoipa::ToSchema,
918)]
1019#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
Modified lib/models/src/git/git_tags.rs View lib/models/src/git/git_tags.rs
@@ -4,7 +4,16 @@
44use uuid::Uuid;
55
66#[derive(
7 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 Default,
13 EnumIter,
14 DeriveActiveEnum,
15 Serialize,
16 Deserialize,
817 utoipa::ToSchema,
918)]
1019#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
Modified lib/models/src/migrate/mod.rs View lib/models/src/migrate/mod.rs
@@ -7,4 +7,4 @@
77 .await?;
88 }
99 Ok(())
10}
No newline at end of file
10}
Modified lib/models/src/repo/repo_branch_protection_whitelists.rs View lib/models/src/repo/repo_branch_protection_whitelists.rs
@@ -3,7 +3,16 @@
33use uuid::Uuid;
44
55#[derive(
6 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
6 Debug,
7 Clone,
8 Copy,
9 PartialEq,
10 Eq,
11 Default,
12 EnumIter,
13 DeriveActiveEnum,
14 Serialize,
15 Deserialize,
716 utoipa::ToSchema,
817)]
918#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
Modified lib/models/src/space/space_members.rs View lib/models/src/space/space_members.rs
@@ -4,7 +4,16 @@
44use uuid::Uuid;
55
66#[derive(
7 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 Default,
13 EnumIter,
14 DeriveActiveEnum,
15 Serialize,
16 Deserialize,
817 utoipa::ToSchema,
918)]
1019#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
@@ -21,7 +30,16 @@
2130}
2231
2332#[derive(
24 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
33 Debug,
34 Clone,
35 Copy,
36 PartialEq,
37 Eq,
38 Default,
39 EnumIter,
40 DeriveActiveEnum,
41 Serialize,
42 Deserialize,
2543 utoipa::ToSchema,
2644)]
2745#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
Modified lib/models/src/space/spaces.rs View lib/models/src/space/spaces.rs
@@ -4,7 +4,16 @@
44use uuid::Uuid;
55
66#[derive(
7 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 Default,
13 EnumIter,
14 DeriveActiveEnum,
15 Serialize,
16 Deserialize,
817 utoipa::ToSchema,
918)]
1019#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
Modified lib/models/src/users/user.rs View lib/models/src/users/user.rs
@@ -5,7 +5,16 @@
55use uuid::Uuid;
66
77#[derive(
8 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
8 Debug,
9 Clone,
10 Copy,
11 PartialEq,
12 Eq,
13 Default,
14 EnumIter,
15 DeriveActiveEnum,
16 Serialize,
17 Deserialize,
918 utoipa::ToSchema,
1019)]
1120#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
@@ -22,7 +31,16 @@
2231}
2332
2433#[derive(
25 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
34 Debug,
35 Clone,
36 Copy,
37 PartialEq,
38 Eq,
39 Default,
40 EnumIter,
41 DeriveActiveEnum,
42 Serialize,
43 Deserialize,
2644 utoipa::ToSchema,
2745)]
2846#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
Modified lib/models/src/users/user_access_keys.rs View lib/models/src/users/user_access_keys.rs
@@ -4,7 +4,15 @@
44use uuid::Uuid;
55
66#[derive(
7 Debug, Clone, Copy, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 EnumIter,
13 DeriveActiveEnum,
14 Serialize,
15 Deserialize,
816 utoipa::ToSchema,
917)]
1018#[serde(rename_all = "lowercase")]
Modified lib/models/src/users/user_push_subscriptions.rs View lib/models/src/users/user_push_subscriptions.rs
@@ -4,7 +4,16 @@
44use uuid::Uuid;
55
66#[derive(
7 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 Default,
13 EnumIter,
14 DeriveActiveEnum,
15 Serialize,
16 Deserialize,
817 utoipa::ToSchema,
918)]
1019#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(16))")]
Modified lib/models/src/users/user_scheduled_messages.rs View lib/models/src/users/user_scheduled_messages.rs
@@ -5,7 +5,16 @@
55use uuid::Uuid;
66
77#[derive(
8 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
8 Debug,
9 Clone,
10 Copy,
11 PartialEq,
12 Eq,
13 Default,
14 EnumIter,
15 DeriveActiveEnum,
16 Serialize,
17 Deserialize,
918 utoipa::ToSchema,
1019)]
1120#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(16))")]
Modified lib/models/src/users/user_sessions.rs View lib/models/src/users/user_sessions.rs
@@ -4,7 +4,16 @@
44use uuid::Uuid;
55
66#[derive(
7 Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 Default,
13 EnumIter,
14 DeriveActiveEnum,
15 Serialize,
16 Deserialize,
817 utoipa::ToSchema,
918)]
1019#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
Modified lib/models/src/users/user_ssh_keys.rs View lib/models/src/users/user_ssh_keys.rs
@@ -4,7 +4,15 @@
44use uuid::Uuid;
55
66#[derive(
7 Debug, Clone, Copy, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize,
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 EnumIter,
13 DeriveActiveEnum,
14 Serialize,
15 Deserialize,
816 utoipa::ToSchema,
917)]
1018#[serde(rename_all = "snake_case")]
Modified lib/models/src/lib.rs View lib/models/src/lib.rs
@@ -2,11 +2,12 @@
22pub mod dir;
33pub mod git;
44pub mod issue;
5pub mod migrate;
56pub mod products;
67pub mod pull_request;
78pub mod repo;
9pub mod room;
810pub mod space;
911pub mod system;
1012pub mod users;
1113pub mod webhook;
12pub mod migrate;
No newline at end of file
Modified lib/pow-solver/src/challenge.rs View lib/pow-solver/src/challenge.rs
@@ -99,4 +99,4 @@
9999 assert_eq!(solve("find-nothing", 256, 10), None);
100100 assert_eq!(solve("find-nothing", 0, 0), None);
101101 }
102}
No newline at end of file
102}
Modified lib/pow-solver/src/lib.rs View lib/pow-solver/src/lib.rs
@@ -1,6 +1,6 @@
11pub mod challenge;
22
3pub use challenge::{check, solve, Challenge};
3pub use challenge::{Challenge, check, solve};
44
55#[cfg(target_arch = "wasm32")]
6pub mod wasm;
No newline at end of file
6pub mod wasm;
Modified lib/pow-solver/src/wasm.rs View lib/pow-solver/src/wasm.rs
@@ -18,4 +18,4 @@
1818 .parse()
1919 .map_err(|e| JsError::new(&format!("invalid answer: {e}")))?;
2020 Ok(check_native(nonce, difficulty, parsed))
21}
No newline at end of file
21}
Modified lib/session/src/share/guard.rs View lib/session/src/share/guard.rs
@@ -27,7 +27,12 @@
2727 ) -> Result<Option<SessionWithUser>, SessionError>;
2828}
2929
30pub async fn session_guard<S>(State(service): State<S>, session: Session, request: Request, next: Next) -> Response
30pub async fn session_guard<S>(
31 State(service): State<S>,
32 session: Session,
33 request: Request,
34 next: Next,
35) -> Response
3136where
3237 S: SessionGuard,
3338{
@@ -57,4 +62,4 @@
5762 return SessionError::Unauthorized.into_response();
5863 }
5964 next.run(request).await
60}
No newline at end of file
65}
Modified lib/session/src/share/mod.rs View lib/session/src/share/mod.rs
@@ -1,2 +1,2 @@
11pub mod guard;
2pub mod session;
No newline at end of file
2pub mod session;
Modified lib/session/src/share/session.rs View lib/session/src/share/session.rs
@@ -40,4 +40,4 @@
4040 return Err(SessionError::Unauthorized);
4141 }
4242 Ok(())
43}
No newline at end of file
43}
Modified lib/session/src/config.rs View lib/session/src/config.rs
@@ -125,7 +125,10 @@
125125 );
126126 }
127127 if config.cookie_prefix_host == Some(true)
128 && config.cookie_domain.as_deref().is_some_and(|domain| !domain.is_empty())
128 && config
129 .cookie_domain
130 .as_deref()
131 .is_some_and(|domain| !domain.is_empty())
129132 {
130133 return Err(
131134 "cookie_prefix_host = true forbids a cookie_domain: __Host- cookies must not carry \
@@ -144,8 +147,16 @@
144147 check_duration("max_lifespan_secs", config.max_lifespan_secs, false)?;
145148 check_duration("memory_lifespan_secs", config.memory_lifespan_secs, true)?;
146149 check_duration("cookie_max_age_secs", config.cookie_max_age_secs, false)?;
147 check_duration("db_update_interval_secs", config.db_update_interval_secs, true)?;
148 check_duration("purge_database_update_secs", config.purge_database_update_secs, true)?;
150 check_duration(
151 "db_update_interval_secs",
152 config.db_update_interval_secs,
153 true,
154 )?;
155 check_duration(
156 "purge_database_update_secs",
157 config.purge_database_update_secs,
158 true,
159 )?;
149160
150161 if let (Some(lifespan), Some(max_lifespan)) = (config.lifespan_secs, config.max_lifespan_secs)
151162 && max_lifespan < lifespan
@@ -204,4 +215,4 @@
204215 .map_err(|err| format!("invalid base64 session key: {err}"))?;
205216
206217 Key::try_from(bytes.as_slice()).map_err(|err| err.to_string())
207}
No newline at end of file
218}
Modified lib/session/src/error.rs View lib/session/src/error.rs
@@ -35,12 +35,13 @@
3535impl IntoResponse for SessionError {
3636 fn into_response(self) -> Response {
3737 match self {
38 Self::Unauthorized => AppResponseData::<NoData>::err(401, "unauthorized")
39 .into_response(),
38 Self::Unauthorized => {
39 AppResponseData::<NoData>::err(401, "unauthorized").into_response()
40 }
4041 Self::Database(err) => {
4142 tracing::error!(error = %err, "session store failure");
4243 AppResponseData::<NoData>::err(500, "internal error").into_response()
4344 }
4445 }
4546 }
46}
No newline at end of file
47}
Modified lib/session/src/lib.rs View lib/session/src/lib.rs
@@ -6,10 +6,10 @@
66
77pub use config::build_session_config;
88pub use error::SessionError;
9pub use share::guard::{session_guard, SessionGuard, SessionWithUser};
9pub use share::guard::{SessionGuard, SessionWithUser, session_guard};
1010pub use share::session::{
11 apply_login, pwd_version_matches, require_uid, session_token_key, validate_session_pwd_version,
12 PWD_VERSION_KEY,
11 PWD_VERSION_KEY, apply_login, pwd_version_matches, require_uid, session_token_key,
12 validate_session_pwd_version,
1313};
14pub use store::{Session, SessionLayer, SessionRedisPool, SessionStore, ReadOnlySession};
15pub use user::{AuthUser, SessionUser, SessionUserMut, UID_KEY, USERS_KEY};
No newline at end of file
14pub use store::{ReadOnlySession, Session, SessionLayer, SessionRedisPool, SessionStore};
15pub use user::{AuthUser, SessionUser, SessionUserMut, UID_KEY, USERS_KEY};
Modified lib/session/src/store.rs View lib/session/src/store.rs
@@ -140,7 +140,9 @@
140140 async fn count(&self, table_name: &str) -> Result<i64, DatabaseError> {
141141 ensure_table(table_name)?;
142142
143 let count = scan_keys(&self.redis, &format!("{table_name}:*")).await?.len() as i64;
143 let count = scan_keys(&self.redis, &format!("{table_name}:*"))
144 .await?
145 .len() as i64;
144146
145147 Ok(count)
146148 }
@@ -194,8 +196,11 @@
194196 let key = session_key(id, table_name);
195197 let mut conn = self.redis.connection_clone();
196198
197 dispatch!(conn, |c| redis::cmd("EXISTS").arg(&key).query_async(c).await)
198 .map_err(|err| DatabaseError::GenericSelectError(err.to_string()))
199 dispatch!(conn, |c| redis::cmd("EXISTS")
200 .arg(&key)
201 .query_async(c)
202 .await)
203 .map_err(|err| DatabaseError::GenericSelectError(err.to_string()))
199204 }
200205
201206 async fn delete_all(&self, table_name: &str) -> Result<(), DatabaseError> {
@@ -263,4 +268,4 @@
263268 }
264269
265270 Ok(())
266}
No newline at end of file
271}
Modified lib/session/src/user.rs View lib/session/src/user.rs
@@ -107,4 +107,4 @@
107107 self.set(USERS_KEY, users);
108108 true
109109 }
110}
No newline at end of file
110}
Modified lib/space/src/handlers/create_join_rule.rs View lib/space/src/handlers/create_join_rule.rs
@@ -29,8 +29,6 @@
2929 Json(cmd): Json<CreateJoinRuleCmd>,
3030) -> Result<AppResponseData<JoinRuleDto>, ApiError> {
3131 let uid = require_uid(&session)?;
32 let dto = service
33 .create_join_rule(uid, &path.space_name, cmd)
34 .await?;
32 let dto = service.create_join_rule(uid, &path.space_name, cmd).await?;
3533 Ok(AppResponseData::ok(dto))
3634}
Modified lib/space/src/handlers/get_member.rs View lib/space/src/handlers/get_member.rs
@@ -33,8 +33,6 @@
3333 Path(path): Path<MemberPath>,
3434) -> Result<AppResponseData<MemberDto>, ApiError> {
3535 let uid = require_uid(&session)?;
36 let member = service
37 .get_member(uid, &path.space_name, path.uid)
38 .await?;
36 let member = service.get_member(uid, &path.space_name, path.uid).await?;
3937 Ok(AppResponseData::ok(member))
4038}
Modified lib/space/src/handlers/get_space_image.rs View lib/space/src/handlers/get_space_image.rs
@@ -22,6 +22,8 @@
2222 Path(path): Path<SpaceImageKindPath>,
2323) -> Result<AppResponseData<Option<SpaceImageDto>>, ApiError> {
2424 let caller = session.uid();
25 let img = service.get_space_image(caller, &path.space_name, path.kind).await?;
25 let img = service
26 .get_space_image(caller, &path.space_name, path.kind)
27 .await?;
2628 Ok(AppResponseData::ok(img))
2729}
Modified lib/space/src/handlers/mod.rs View lib/space/src/handlers/mod.rs
@@ -5,8 +5,12 @@
55pub mod create_dir_node;
66pub mod create_group;
77pub mod create_invite;
8pub mod create_join_rule;
9pub mod create_room;
810pub mod create_space;
911pub mod delete_group;
12pub mod delete_join_rule;
13pub mod delete_room;
1014pub mod delete_space;
1115pub mod delete_space_avatar;
1216pub mod delete_space_image;
@@ -17,17 +21,14 @@
1721pub mod get_space_avatar;
1822pub mod get_space_dir_tree;
1923pub mod get_space_image;
20pub mod create_join_rule;
21pub mod delete_join_rule;
2224pub mod join_group_self;
2325pub mod leave_group_self;
24pub mod list_join_rules;
25pub mod update_join_rule;
2626pub mod list_bans;
2727pub mod list_dir_group_overwrites;
2828pub mod list_dir_user_overwrites;
2929pub mod list_groups;
3030pub mod list_invites;
31pub mod list_join_rules;
3132pub mod list_members;
3233pub mod list_spaces;
3334pub mod move_dir_node;
@@ -43,6 +44,7 @@
4344pub mod update_ban;
4445pub mod update_dir_node;
4546pub mod update_group;
47pub mod update_join_rule;
4648pub mod update_member;
4749pub mod update_space;
4850pub mod upload_space_avatar;
Modified lib/space/src/handlers/move_dir_node.rs View lib/space/src/handlers/move_dir_node.rs
@@ -3,7 +3,7 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::dir::{DirPath, DirNodeDto, MoveDirCmd};
6use crate::services::dir::{DirNodeDto, DirPath, MoveDirCmd};
77use crate::services::service::SpaceService;
88use session::share::session::require_uid;
99
Modified lib/space/src/handlers/unban_user.rs View lib/space/src/handlers/unban_user.rs
@@ -24,6 +24,8 @@
2424 Path(path): Path<MemberPath>,
2525) -> Result<AppResponseData<NoData>, ApiError> {
2626 let caller = require_uid(&session)?;
27 service.unban_user(caller, &path.space_name, path.uid).await?;
27 service
28 .unban_user(caller, &path.space_name, path.uid)
29 .await?;
2830 Ok(AppResponseData::empty())
2931}
Modified lib/space/src/handlers/update_dir_node.rs View lib/space/src/handlers/update_dir_node.rs
@@ -3,7 +3,7 @@
33use session::Session;
44
55use crate::errors::ApiError;
6use crate::services::dir::{DirPath, DirNodeDto, UpdateDirCmd};
6use crate::services::dir::{DirNodeDto, DirPath, UpdateDirCmd};
77use crate::services::service::SpaceService;
88use session::share::session::require_uid;
99
Modified lib/space/src/services/avatar.rs View lib/space/src/services/avatar.rs
@@ -1,5 +1,5 @@
1use base64::Engine;
12use base64::engine::general_purpose::STANDARD;
2use base64::Engine;
33use models::space::spaces;
44use sea_orm::{ActiveModelTrait, Set};
55use serde::{Deserialize, Serialize};
Modified lib/space/src/services/bans.rs View lib/space/src/services/bans.rs
@@ -292,7 +292,10 @@
292292
293293 let max_pagination = self.config().space.max_pagination;
294294 let default_pagination = self.config().space.default_pagination;
295 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
295 let limit = q
296 .limit
297 .unwrap_or(default_pagination)
298 .clamp(1, max_pagination);
296299 let offset = q.offset.unwrap_or(0);
297300
298301 let select = space_bans::Entity::find()
Modified lib/space/src/services/dir.rs View lib/space/src/services/dir.rs
@@ -1,14 +1,13 @@
11use std::collections::{HashMap, HashSet};
22
3use models::dir::dirs::{self, DirKind};
43use models::dir::dir_overwrite_group;
54use models::dir::dir_overwrite_users;
5use models::dir::dirs::{self, DirKind};
66use models::space::space_groups;
77use models::space::space_member_groups;
88use models::users::user;
99use sea_orm::{
10 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set,
11 TransactionTrait,
10 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set, TransactionTrait,
1211};
1312use serde::{Deserialize, Serialize};
1413use time::OffsetDateTime;
@@ -18,7 +17,7 @@
1817use crate::errors::SpaceError;
1918use crate::services::service::SpaceService;
2019use crate::services::spaces::UserBriefDto;
21use crate::share::authz::{effective_permissions, require_perm, MANAGE_OVERWRITES};
20use crate::share::authz::{MANAGE_OVERWRITES, effective_permissions, require_perm};
2221use crate::share::lookup::lookup_space_by_name;
2322
2423pub const MAX_DIR_NODE_NAME: usize = 255;
@@ -146,10 +145,16 @@
146145 .iter()
147146 .filter(|g| my_group_ids.contains(&g.group_id))
148147 .collect();
149 if matching.iter().any(|g| (g.deny_mask & PERM_DIR_VISIBLE) != 0) {
148 if matching
149 .iter()
150 .any(|g| (g.deny_mask & PERM_DIR_VISIBLE) != 0)
151 {
150152 return false;
151153 }
152 if matching.iter().any(|g| (g.allow_mask & PERM_DIR_VISIBLE) != 0) {
154 if matching
155 .iter()
156 .any(|g| (g.allow_mask & PERM_DIR_VISIBLE) != 0)
157 {
153158 return true;
154159 }
155160 true
@@ -177,20 +182,26 @@
177182
178183 let name = cmd.name.trim();
179184 if name.is_empty() || name.chars().count() > MAX_DIR_NODE_NAME {
180 return Err(SpaceError::BadRequest("dir name is empty or too long".to_string()));
185 return Err(SpaceError::BadRequest(
186 "dir name is empty or too long".to_string(),
187 ));
181188 }
182189
183190 if let Some(p) = cmd.parent_dir_id {
184191 let parent_node = self.dir_in_space(space.id, p).await?;
185192 if parent_node.kind != DirKind::Dir {
186 return Err(SpaceError::BadRequest("parent node must be a dir".to_string()));
193 return Err(SpaceError::BadRequest(
194 "parent node must be a dir".to_string(),
195 ));
187196 }
188197 }
189198
190199 let position = match cmd.position {
191200 Some(pos) => {
192201 if pos < 0 {
193 return Err(SpaceError::BadRequest("position must be non-negative".to_string()));
202 return Err(SpaceError::BadRequest(
203 "position must be non-negative".to_string(),
204 ));
194205 }
195206 pos
196207 }
@@ -266,7 +277,8 @@
266277 .filter(space_member_groups::Column::UserId.eq(caller))
267278 .all(self.db().read())
268279 .await?;
269 let my_group_ids: HashSet<Uuid> = my_member_groups.into_iter().map(|mg| mg.group_id).collect();
280 let my_group_ids: HashSet<Uuid> =
281 my_member_groups.into_iter().map(|mg| mg.group_id).collect();
270282
271283 let mut group_ow_map: HashMap<Uuid, Vec<dir_overwrite_group::Model>> = HashMap::new();
272284 for go in group_ows {
@@ -347,7 +359,10 @@
347359 Some(p) if truly_visible_set.contains(&p) => Some(p),
348360 _ => None,
349361 };
350 children_map.entry(parent_id).or_default().push(DirNodeDto::from(n));
362 children_map
363 .entry(parent_id)
364 .or_default()
365 .push(DirNodeDto::from(n));
351366 }
352367
353368 fn assemble_tree(
@@ -386,7 +401,9 @@
386401
387402 let name = cmd.name.trim();
388403 if name.is_empty() || name.chars().count() > MAX_DIR_NODE_NAME {
389 return Err(SpaceError::BadRequest("dir name is empty or too long".to_string()));
404 return Err(SpaceError::BadRequest(
405 "dir name is empty or too long".to_string(),
406 ));
390407 }
391408
392409 let now = OffsetDateTime::now_utc();
@@ -417,11 +434,15 @@
417434 Some(None) => None,
418435 Some(Some(p)) => {
419436 if p == dir_id {
420 return Err(SpaceError::BadRequest("cannot set parent to self".to_string()));
437 return Err(SpaceError::BadRequest(
438 "cannot set parent to self".to_string(),
439 ));
421440 }
422441 let parent_node = self.dir_in_space(space.id, p).await?;
423442 if parent_node.kind != DirKind::Dir {
424 return Err(SpaceError::BadRequest("parent node must be a dir".to_string()));
443 return Err(SpaceError::BadRequest(
444 "parent node must be a dir".to_string(),
445 ));
425446 }
426447
427448 let mut curr = parent_node.parent_dir_id;
@@ -445,7 +466,9 @@
445466 let new_position = match cmd.position {
446467 Some(pos) => {
447468 if pos < 0 {
448 return Err(SpaceError::BadRequest("position must be non-negative".to_string()));
469 return Err(SpaceError::BadRequest(
470 "position must be non-negative".to_string(),
471 ));
449472 }
450473 pos
451474 }
@@ -605,7 +628,9 @@
605628 .ok_or(SpaceError::NotFound)?;
606629
607630 if cmd.allow_mask < 0 || cmd.deny_mask < 0 {
608 return Err(SpaceError::BadRequest("masks must be non-negative".to_string()));
631 return Err(SpaceError::BadRequest(
632 "masks must be non-negative".to_string(),
633 ));
609634 }
610635
611636 let existing = dir_overwrite_group::Entity::find_by_id((dir_id, group_id))
@@ -714,7 +739,9 @@
714739 .ok_or(SpaceError::NotFound)?;
715740
716741 if cmd.allow_mask < 0 || cmd.deny_mask < 0 {
717 return Err(SpaceError::BadRequest("masks must be non-negative".to_string()));
742 return Err(SpaceError::BadRequest(
743 "masks must be non-negative".to_string(),
744 ));
718745 }
719746
720747 let existing = dir_overwrite_users::Entity::find_by_id((dir_id, user_id))
Modified lib/space/src/services/groups.rs View lib/space/src/services/groups.rs
@@ -16,7 +16,7 @@
1616use crate::errors::SpaceError;
1717use crate::services::service::SpaceService;
1818use crate::services::spaces::{PageQuery, UserBriefDto};
19use crate::share::authz::{effective_permissions, require_perm, ALL_PERMISSIONS, MANAGE_ROLES};
19use crate::share::authz::{ALL_PERMISSIONS, MANAGE_ROLES, effective_permissions, require_perm};
2020use crate::share::lookup::lookup_space_by_name;
2121
2222#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
@@ -88,7 +88,9 @@
8888
8989 for perm in &perms {
9090 if !ALL_PERMISSIONS.contains(perm) {
91 return Err(SpaceError::BadRequest(format!("unknown permission: {perm}")));
91 return Err(SpaceError::BadRequest(format!(
92 "unknown permission: {perm}"
93 )));
9294 }
9395 }
9496
@@ -121,7 +123,9 @@
121123
122124 let name = cmd.name.trim();
123125 if name.is_empty() || name.chars().count() > self.config().space.max_group_name_len {
124 return Err(SpaceError::BadRequest("group name is empty or too long".to_string()));
126 return Err(SpaceError::BadRequest(
127 "group name is empty or too long".to_string(),
128 ));
125129 }
126130
127131 let existing = space_groups::Entity::find()
@@ -258,7 +262,9 @@
258262 if let Some(new_name) = cmd.name {
259263 let name = new_name.trim();
260264 if name.is_empty() || name.chars().count() > self.config().space.max_group_name_len {
261 return Err(SpaceError::BadRequest("group name is empty or too long".to_string()));
265 return Err(SpaceError::BadRequest(
266 "group name is empty or too long".to_string(),
267 ));
262268 }
263269 let existing = space_groups::Entity::find()
264270 .filter(space_groups::Column::SpaceId.eq(space.id))
@@ -387,7 +393,10 @@
387393
388394 let max_pagination = self.config().space.max_pagination;
389395 let default_pagination = self.config().space.default_pagination;
390 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
396 let limit = q
397 .limit
398 .unwrap_or(default_pagination)
399 .clamp(1, max_pagination);
391400 let offset = q.offset.unwrap_or(0);
392401
393402 let select = space_groups::Entity::find()
Modified lib/space/src/services/images.rs View lib/space/src/services/images.rs
@@ -1,5 +1,5 @@
1use base64::Engine;
12use base64::engine::general_purpose::STANDARD;
2use base64::Engine;
33use models::space::spaces;
44use sea_orm::{ActiveModelTrait, Set};
55use serde::{Deserialize, Serialize};
@@ -92,12 +92,16 @@
9292 if let Some(w) = cmd.width
9393 && w < 0
9494 {
95 return Err(SpaceError::BadRequest("width must be non-negative".to_string()));
95 return Err(SpaceError::BadRequest(
96 "width must be non-negative".to_string(),
97 ));
9698 }
9799 if let Some(h) = cmd.height
98100 && h < 0
99101 {
100 return Err(SpaceError::BadRequest("height must be non-negative".to_string()));
102 return Err(SpaceError::BadRequest(
103 "height must be non-negative".to_string(),
104 ));
101105 }
102106
103107 let space_id = space.id;
Modified lib/space/src/services/invites.rs View lib/space/src/services/invites.rs
@@ -1,12 +1,12 @@
11use std::collections::HashMap;
22
3use models::space::space_group_permissions;
4use models::space::space_groups;
35use models::space::space_invite;
46use models::space::space_invite_groups;
57use models::space::space_member_groups;
68use models::space::space_members::{self, MemberStatus, MessageNotifications};
79use models::space::spaces;
8use models::space::space_group_permissions;
9use models::space::space_groups;
1010use models::users::user;
1111use models::users::user_emails;
1212use sea_orm::sea_query::Expr;
@@ -22,7 +22,7 @@
2222use crate::errors::SpaceError;
2323use crate::services::service::SpaceService;
2424use crate::services::spaces::{PageQuery, UserBriefDto};
25use crate::share::authz::{assert_not_banned, effective_permissions, require_perm, MANAGE_INVITES};
25use crate::share::authz::{MANAGE_INVITES, assert_not_banned, effective_permissions, require_perm};
2626use crate::share::lookup::lookup_space_by_name;
2727
2828#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
@@ -337,7 +337,11 @@
337337 })
338338 }
339339
340 pub async fn accept_invite(&self, caller: Uuid, cmd: AcceptInviteCmd) -> Result<(), SpaceError> {
340 pub async fn accept_invite(
341 &self,
342 caller: Uuid,
343 cmd: AcceptInviteCmd,
344 ) -> Result<(), SpaceError> {
341345 let invite = space_invite::Entity::find()
342346 .filter(space_invite::Column::AcceptCode.eq(&cmd.accept_code))
343347 .one(self.db().read())
@@ -380,7 +384,9 @@
380384 .await?;
381385 let group_ids: Vec<Uuid> = inv_groups.into_iter().map(|g| g.group_id).collect();
382386 if group_ids.is_empty() {
383 return Err(SpaceError::Conflict("invite has no valid groups".to_string()));
387 return Err(SpaceError::Conflict(
388 "invite has no valid groups".to_string(),
389 ));
384390 }
385391
386392 let valid_count = space_groups::Entity::find()
@@ -389,7 +395,9 @@
389395 .count(self.db().read())
390396 .await?;
391397 if valid_count != group_ids.len() as u64 {
392 return Err(SpaceError::Conflict("invite has invalid groups".to_string()));
398 return Err(SpaceError::Conflict(
399 "invite has invalid groups".to_string(),
400 ));
393401 }
394402
395403 let space = spaces::Entity::find_by_id(invite.space_id)
@@ -476,10 +484,7 @@
476484
477485 spaces::Entity::update_many()
478486 .filter(spaces::Column::Id.eq(space.id))
479 .col_expr(
480 spaces::Column::NumsMembers,
481 Expr::cust("nums_members + 1"),
482 )
487 .col_expr(spaces::Column::NumsMembers, Expr::cust("nums_members + 1"))
483488 .exec(txn)
484489 .await?;
485490
@@ -504,7 +509,10 @@
504509
505510 let max_pagination = self.config().space.max_pagination;
506511 let default_pagination = self.config().space.default_pagination;
507 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
512 let limit = q
513 .limit
514 .unwrap_or(default_pagination)
515 .clamp(1, max_pagination);
508516 let offset = q.offset.unwrap_or(0);
509517
510518 let select = space_invite::Entity::find()
@@ -526,7 +534,10 @@
526534
527535 let mut groups_map: HashMap<Uuid, Vec<Uuid>> = HashMap::new();
528536 for ig in all_invite_groups {
529 groups_map.entry(ig.invite_id).or_default().push(ig.group_id);
537 groups_map
538 .entry(ig.invite_id)
539 .or_default()
540 .push(ig.group_id);
530541 }
531542
532543 let user_ids: Vec<Uuid> = rows.iter().filter_map(|i| i.user_id).collect();
Modified lib/space/src/services/join_rules.rs View lib/space/src/services/join_rules.rs
@@ -1,4 +1,6 @@
1use sea_orm::{ActiveModelTrait, ColumnTrait, Condition, ConnectionTrait, EntityTrait, QueryFilter, Set};
1use sea_orm::{
2 ActiveModelTrait, ColumnTrait, Condition, ConnectionTrait, EntityTrait, QueryFilter, Set,
3};
24use serde::{Deserialize, Serialize};
35use time::OffsetDateTime;
46use utoipa::{IntoParams, ToSchema};
@@ -10,7 +12,7 @@
1012use models::space::space_member_groups;
1113use models::space::space_members::{self, MemberStatus};
1214
13use crate::errors::{is_unique_violation, SpaceError};
15use crate::errors::{SpaceError, is_unique_violation};
1416use crate::services::service::SpaceService;
1517use crate::share::authz::effective_permissions;
1618use crate::share::lookup::lookup_space_by_name;
Modified lib/space/src/services/members.rs View lib/space/src/services/members.rs
@@ -15,7 +15,7 @@
1515use crate::errors::SpaceError;
1616use crate::services::service::SpaceService;
1717use crate::services::spaces::{PageQuery, UserBriefDto};
18use crate::share::authz::{assert_not_banned, require_perm, MANAGE_MEMBERS};
18use crate::share::authz::{MANAGE_MEMBERS, assert_not_banned, require_perm};
1919use crate::share::lookup::lookup_space_by_name;
2020
2121#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
@@ -146,10 +146,7 @@
146146 let m = am.update(txn).await?;
147147 spaces::Entity::update_many()
148148 .filter(spaces::Column::Id.eq(space.id))
149 .col_expr(
150 spaces::Column::NumsMembers,
151 Expr::cust("nums_members + 1"),
152 )
149 .col_expr(spaces::Column::NumsMembers, Expr::cust("nums_members + 1"))
153150 .exec(txn)
154151 .await?;
155152 Ok(m)
@@ -188,10 +185,7 @@
188185 let m = member_row.insert(txn).await?;
189186 spaces::Entity::update_many()
190187 .filter(spaces::Column::Id.eq(space.id))
191 .col_expr(
192 spaces::Column::NumsMembers,
193 Expr::cust("nums_members + 1"),
194 )
188 .col_expr(spaces::Column::NumsMembers, Expr::cust("nums_members + 1"))
195189 .exec(txn)
196190 .await?;
197191 Ok(m)
@@ -200,6 +194,41 @@
200194 .await?
201195 };
202196
197 if let Some(ref mut lc) = self.state.livechat.clone() {
198 let public_rooms = models::room::rooms::Entity::find()
199 .filter(models::room::rooms::Column::SpaceId.eq(space.id))
200 .filter(models::room::rooms::Column::IsPrivate.eq(false))
201 .filter(models::room::rooms::Column::IsActive.eq(true))
202 .all(self.db().read())
203 .await?;
204
205 for room in &public_rooms {
206 let join_req = lc::proto_gen::livechat::member::JoinRoomRequest {
207 user_id: saved.user_id.to_string(),
208 room_id: room.id.to_string(),
209 nick: None,
210 roles: vec![],
211 };
212 if let Err(e) = lc.member_mut().join_room(join_req).await {
213 tracing::warn!(error = %e, room_id = %room.id, user_id = %saved.user_id, "failed to join member to livechat room");
214 }
215
216 let now = OffsetDateTime::now_utc();
217 let rm = models::room::room_members::ActiveModel {
218 room_id: Set(room.id),
219 user_id: Set(saved.user_id),
220 nick: Set(None),
221 joined_at: Set(now),
222 left_at: Set(None),
223 created_at: Set(now),
224 updated_at: Set(Some(now)),
225 };
226 if let Err(e) = rm.insert(self.db().write()).await {
227 tracing::warn!(error = %e, room_id = %room.id, user_id = %saved.user_id, "failed to create room_members record");
228 }
229 }
230 }
231
203232 let user_map = self.load_user_briefs(&[saved.user_id]).await?;
204233 let user_brief = user_map.get(&saved.user_id).cloned();
205234
@@ -406,10 +435,7 @@
406435
407436 spaces::Entity::update_many()
408437 .filter(spaces::Column::Id.eq(space.id))
409 .col_expr(
410 spaces::Column::NumsMembers,
411 Expr::cust("nums_members - 1"),
412 )
438 .col_expr(spaces::Column::NumsMembers, Expr::cust("nums_members - 1"))
413439 .exec(txn)
414440 .await?;
415441
@@ -432,7 +458,10 @@
432458
433459 let max_pagination = self.config().space.max_pagination;
434460 let default_pagination = self.config().space.default_pagination;
435 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
461 let limit = q
462 .limit
463 .unwrap_or(default_pagination)
464 .clamp(1, max_pagination);
436465 let offset = q.offset.unwrap_or(0);
437466
438467 let select = space_members::Entity::find()
Modified lib/space/src/services/mod.rs View lib/space/src/services/mod.rs
@@ -3,8 +3,9 @@
33pub mod dir;
44pub mod groups;
55pub mod images;
6pub mod invites;
67pub mod join_rules;
7pub mod invites;
88pub mod members;
9pub mod rooms;
910pub mod service;
1011pub mod spaces;
Modified lib/space/src/services/service.rs View lib/space/src/services/service.rs
@@ -2,13 +2,13 @@
22
33use async_trait::async_trait;
44use config::AppConfig;
5use email::EmailService;
56use export::{AppDatabase, AppState, AppStorage};
6use email::EmailService;
77use models::users::user;
88use models::users::user_sessions;
99use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
10use session::SessionError;
1011use session::share::guard::{SessionGuard, SessionWithUser};
11use session::SessionError;
1212
1313use crate::errors::SpaceError;
1414
Modified lib/space/src/services/spaces.rs View lib/space/src/services/spaces.rs
@@ -1,7 +1,7 @@
11use std::collections::HashMap;
22
3use models::space::space_members::{self, MemberStatus, MessageNotifications};
34use models::space::spaces::{self, AllowJoinLevel};
4use models::space::space_members::{self, MemberStatus, MessageNotifications};
55use models::users::user;
66use sea_orm::{
77 ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
@@ -157,15 +157,23 @@
157157 Ok(map)
158158 }
159159
160 pub async fn create_space(&self, caller: Uuid, cmd: CreateSpaceCmd) -> Result<SpaceDto, SpaceError> {
160 pub async fn create_space(
161 &self,
162 caller: Uuid,
163 cmd: CreateSpaceCmd,
164 ) -> Result<SpaceDto, SpaceError> {
161165 let name = cmd.name.trim();
162166 if name.is_empty() || name.chars().count() > self.config().space.max_space_name_len {
163 return Err(SpaceError::BadRequest("space name is empty or too long".to_string()));
167 return Err(SpaceError::BadRequest(
168 "space name is empty or too long".to_string(),
169 ));
164170 }
165171 if let Some(desc) = &cmd.description
166172 && desc.chars().count() > self.config().space.max_space_desc_len
167173 {
168 return Err(SpaceError::BadRequest("space description is too long".to_string()));
174 return Err(SpaceError::BadRequest(
175 "space description is too long".to_string(),
176 ));
169177 }
170178 let lower = name.to_lowercase();
171179 if !is_valid_lower_name(&lower) {
@@ -348,7 +356,9 @@
348356 if let Some(new_name) = cmd.name {
349357 let name = new_name.trim();
350358 if name.is_empty() || name.chars().count() > self.config().space.max_space_name_len {
351 return Err(SpaceError::BadRequest("space name is empty or too long".to_string()));
359 return Err(SpaceError::BadRequest(
360 "space name is empty or too long".to_string(),
361 ));
352362 }
353363 let lower = name.to_lowercase();
354364 if !is_valid_lower_name(&lower) {
@@ -375,7 +385,9 @@
375385 if let Some(d) = &desc
376386 && d.chars().count() > self.config().space.max_space_desc_len
377387 {
378 return Err(SpaceError::BadRequest("space description is too long".to_string()));
388 return Err(SpaceError::BadRequest(
389 "space description is too long".to_string(),
390 ));
379391 }
380392 am.description = Set(desc);
381393 }
@@ -396,7 +408,9 @@
396408 if let Some(w) = &welcome
397409 && w.chars().count() > self.config().space.max_space_desc_len
398410 {
399 return Err(SpaceError::BadRequest("welcome message is too long".to_string()));
411 return Err(SpaceError::BadRequest(
412 "welcome message is too long".to_string(),
413 ));
400414 }
401415 am.welcome_message = Set(welcome);
402416 }
@@ -455,7 +469,10 @@
455469 pub async fn list_spaces(&self, caller: Uuid, q: PageQuery) -> Result<SpacePage, SpaceError> {
456470 let max_pagination = self.config().space.max_pagination;
457471 let default_pagination = self.config().space.default_pagination;
458 let limit = q.limit.unwrap_or(default_pagination).clamp(1, max_pagination);
472 let limit = q
473 .limit
474 .unwrap_or(default_pagination)
475 .clamp(1, max_pagination);
459476 let offset = q.offset.unwrap_or(0);
460477
461478 let select = spaces::Entity::find()
Modified lib/space/src/share/mod.rs View lib/space/src/share/mod.rs
@@ -1,3 +1,3 @@
11pub mod authz;
22pub mod guard;
3pub mod lookup;
No newline at end of file
3pub mod lookup;
Modified lib/space/src/errors.rs View lib/space/src/errors.rs
@@ -1,9 +1,9 @@
11use std::fmt;
22
3use axum::Json;
34use axum::extract::rejection::{PathRejection, QueryRejection};
45use axum::http::StatusCode;
56use axum::response::{IntoResponse, Response};
6use axum::Json;
77use export::rest::{AppResponseData, NoData};
88use utoipa::IntoResponses;
99
@@ -78,6 +78,12 @@
7878 }
7979}
8080
81impl From<lc::Error> for SpaceError {
82 fn from(error: lc::Error) -> Self {
83 Self::Internal(format!("livechat error: {error}"))
84 }
85}
86
8187impl From<serde_json::Error> for SpaceError {
8288 fn from(error: serde_json::Error) -> Self {
8389 Self::BadRequest(format!("invalid payload: {error}"))
Modified lib/space/src/routes.rs View lib/space/src/routes.rs
@@ -1,5 +1,5 @@
1use axum::Router;
12use axum::routing::{delete, get, patch, post, put};
2use axum::Router;
33
44use crate::handlers::accept_invite::accept_invite;
55use crate::handlers::add_member::add_member;
@@ -9,9 +9,11 @@
99use crate::handlers::create_group::create_group;
1010use crate::handlers::create_invite::create_invite;
1111use crate::handlers::create_join_rule::create_join_rule;
12use crate::handlers::create_room::create_room;
1213use crate::handlers::create_space::create_space;
1314use crate::handlers::delete_group::delete_group;
1415use crate::handlers::delete_join_rule::delete_join_rule;
16use crate::handlers::delete_room::delete_room;
1517use crate::handlers::delete_space::delete_space;
1618use crate::handlers::delete_space_avatar::delete_space_avatar;
1719use crate::handlers::delete_space_image::delete_space_image;
@@ -56,26 +58,14 @@
5658pub fn router(service: SpaceService) -> Router<SpaceService> {
5759 let public: Router<SpaceService> = Router::new()
5860 .route("/spaces/{space_name}", get(get_space))
59 .route(
60 "/spaces/{space_name}/groups/{group_id}",
61 get(get_group),
62 )
63 .route(
64 "/spaces/{space_name}/avatar",
65 get(get_space_avatar),
66 )
67 .route(
68 "/spaces/{space_name}/images/{kind}",
69 get(get_space_image),
70 );
61 .route("/spaces/{space_name}/groups/{group_id}", get(get_group))
62 .route("/spaces/{space_name}/avatar", get(get_space_avatar))
63 .route("/spaces/{space_name}/images/{kind}", get(get_space_image));
7164
7265 let protected = Router::new()
7366 .route("/spaces", post(create_space).get(list_spaces))
67 .route("/spaces/invites/accept", post(accept_invite))
7468 .route(
75 "/spaces/invites/accept",
76 post(accept_invite),
77 )
78 .route(
7969 "/spaces/{space_name}",
8070 patch(update_space).delete(delete_space),
8171 )
@@ -87,6 +77,8 @@
8777 "/spaces/{space_name}/images/{kind}",
8878 put(upload_space_image).delete(delete_space_image),
8979 )
80 .route("/spaces/{space_name}/rooms", post(create_room))
81 .route("/spaces/{space_name}/rooms/{room_id}", delete(delete_room))
9082 .route(
9183 "/spaces/{space_name}/dir",
9284 post(create_dir_node).get(get_space_dir_tree),
@@ -95,11 +87,8 @@
9587 "/spaces/{space_name}/dir/repair-parents",
9688 post(repair_dir_parents),
9789 )
90 .route("/spaces/{space_name}/dir/{dir_id}", patch(update_dir_node))
9891 .route(
99 "/spaces/{space_name}/dir/{dir_id}",
100 patch(update_dir_node),
101 )
102 .route(
10392 "/spaces/{space_name}/dir/{dir_id}/position",
10493 patch(move_dir_node),
10594 )
@@ -125,9 +114,7 @@
125114 )
126115 .route(
127116 "/spaces/{space_name}/members/{uid}",
128 get(get_member)
129 .patch(update_member)
130 .delete(remove_member),
117 get(get_member).patch(update_member).delete(remove_member),
131118 )
132119 .route(
133120 "/spaces/{space_name}/groups",
@@ -165,10 +152,7 @@
165152 "/spaces/{space_name}/invites/{invite_id}",
166153 delete(revoke_invite),
167154 )
168 .route(
169 "/spaces/{space_name}/bans",
170 post(ban_user).get(list_bans),
171 )
155 .route("/spaces/{space_name}/bans", post(ban_user).get(list_bans))
172156 .route(
173157 "/spaces/{space_name}/bans/{uid}",
174158 get(get_ban).patch(update_ban).delete(unban_user),
Modified lib/space/Cargo.toml View lib/space/Cargo.toml
@@ -19,4 +19,5 @@
1919uuid = { workspace = true }
2020time = { workspace = true }
2121base64 = { workspace = true }
22lc = { workspace = true }
2223tracing = { workspace = true }
No newline at end of file
Modified lib/storage/src/lib.rs View lib/storage/src/lib.rs
@@ -2,4 +2,4 @@
22pub mod s3;
33
44pub use error::StorageError;
5pub use s3::{ObjectInfo, ObjectPage, S3Client, MAX_PRESIGN_EXPIRY_SECS};
5pub use s3::{MAX_PRESIGN_EXPIRY_SECS, ObjectInfo, ObjectPage, S3Client};
Modified lib/storage/src/s3.rs View lib/storage/src/s3.rs
@@ -4,14 +4,14 @@
44use std::task::{Context as TaskContext, Poll};
55
66use config::s3::S3Config;
7use futures::StreamExt as _;
78use http::HeaderMap;
9use s3::Bucket;
810use s3::bucket::{CHUNK_SIZE, Part};
911use s3::creds::Credentials;
1012use s3::region::Region;
1113use s3::serde_types::{DateTime as S3DateTime, Object as S3Object};
12use s3::Bucket;
1314use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite};
14use futures::StreamExt as _;
1515
1616use crate::error::StorageError;
1717
@@ -112,10 +112,14 @@
112112 ) -> Result<u64, StorageError> {
113113 let first = read_chunk(reader).await?;
114114 if first.len() < CHUNK_SIZE {
115 self.put_with_content_type(key, &first, content_type).await?;
115 self.put_with_content_type(key, &first, content_type)
116 .await?;
116117 return Ok(first.len() as u64);
117118 }
118 let msg = self.bucket.initiate_multipart_upload(key, content_type).await?;
119 let msg = self
120 .bucket
121 .initiate_multipart_upload(key, content_type)
122 .await?;
119123 let path = msg.key;
120124 let upload_id = msg.upload_id;
121125
@@ -210,11 +214,11 @@
210214 key: &str,
211215 writer: &mut W,
212216 ) -> Result<u64, StorageError> {
213 let mut counting = CountingWriter { inner: writer, count: 0 };
214 let status = self
215 .bucket
216 .get_object_to_writer(key, &mut counting)
217 .await?;
217 let mut counting = CountingWriter {
218 inner: writer,
219 count: 0,
220 };
221 let status = self.bucket.get_object_to_writer(key, &mut counting).await?;
218222 if !(200..300).contains(&status) {
219223 return Err(StorageError::UnexpectedStatus {
220224 operation: "get_object",
@@ -240,7 +244,11 @@
240244 status: stream.status_code,
241245 });
242246 }
243 Ok(Box::pin(stream.bytes.map(move |item| item.map_err(StorageError::from))))
247 Ok(Box::pin(
248 stream
249 .bytes
250 .map(move |item| item.map_err(StorageError::from)),
251 ))
244252 }
245253
246254 pub async fn delete(&self, key: &str) -> Result<(), StorageError> {
@@ -321,11 +329,7 @@
321329 &self.bucket
322330 }
323331
324 pub fn presign_get(
325 &self,
326 key: &str,
327 expiry_secs: u32,
328 ) -> Result<String, StorageError> {
332 pub fn presign_get(&self, key: &str, expiry_secs: u32) -> Result<String, StorageError> {
329333 check_expiry(expiry_secs)?;
330334 Ok(self.bucket.presign_get(key, expiry_secs, None)?)
331335 }
@@ -342,11 +346,7 @@
342346 .presign_get(key, expiry_secs, Some(custom_queries))?)
343347 }
344348
345 pub fn presign_put(
346 &self,
347 key: &str,
348 expiry_secs: u32,
349 ) -> Result<String, StorageError> {
349 pub fn presign_put(&self, key: &str, expiry_secs: u32) -> Result<String, StorageError> {
350350 check_expiry(expiry_secs)?;
351351 Ok(self.bucket.presign_put(key, expiry_secs, None)?)
352352 }
@@ -363,11 +363,7 @@
363363 .presign_put(key, expiry_secs, Some(custom_headers))?)
364364 }
365365
366 pub fn presign_delete(
367 &self,
368 key: &str,
369 expiry_secs: u32,
370 ) -> Result<String, StorageError> {
366 pub fn presign_delete(&self, key: &str, expiry_secs: u32) -> Result<String, StorageError> {
371367 check_expiry(expiry_secs)?;
372368 Ok(self.bucket.presign_delete(key, expiry_secs)?)
373369 }
@@ -403,9 +399,13 @@
403399 .as_deref()
404400 .filter(|s| !s.is_empty()),
405401 ) {
406 (Some(access), Some(secret)) => {
407 Ok(Credentials::new(Some(access), Some(secret), None, None, None)?)
408 }
402 (Some(access), Some(secret)) => Ok(Credentials::new(
403 Some(access),
404 Some(secret),
405 None,
406 None,
407 None,
408 )?),
409409 (Some(_), None) => Err(StorageError::InvalidConfig(
410410 "access_key_id provided without secret_access_key".into(),
411411 )),
@@ -484,7 +484,9 @@
484484
485485 #[tokio::test]
486486 async fn read_chunk_respects_chunk_size() {
487 let mut data = (0..CHUNK_SIZE as u32).map(|i| (i % 251) as u8).collect::<Vec<_>>();
487 let mut data = (0..CHUNK_SIZE as u32)
488 .map(|i| (i % 251) as u8)
489 .collect::<Vec<_>>();
488490 let mut cursor = io::Cursor::new(&data);
489491 let first = read_chunk(&mut cursor).await.expect("read");
490492 assert_eq!(first.len(), CHUNK_SIZE);
@@ -501,7 +503,10 @@
501503 async fn counting_writer_tallies_bytes() {
502504 let mut buf = Vec::new();
503505 {
504 let mut cw = CountingWriter { inner: &mut buf, count: 0 };
506 let mut cw = CountingWriter {
507 inner: &mut buf,
508 count: 0,
509 };
505510 cw.write_all(b"hello ").await.expect("write");
506511 cw.write_all(b"world").await.expect("write");
507512 cw.flush().await.expect("flush");
Modified lib/user/src/handlers/add_email.rs View lib/user/src/handlers/add_email.rs
@@ -1,5 +1,5 @@
1use axum::Json;
12use axum::extract::State;
2use axum::Json;
33use export::rest::AppResponseData;
44use session::Session;
55
Modified lib/user/src/handlers/create_meme.rs View lib/user/src/handlers/create_meme.rs
@@ -1,5 +1,5 @@
1use axum::Json;
12use axum::extract::State;
2use axum::Json;
33use export::rest::AppResponseData;
44use session::Session;
55
Modified lib/user/src/handlers/save_message.rs View lib/user/src/handlers/save_message.rs
@@ -1,5 +1,5 @@
1use axum::Json;
12use axum::extract::State;
2use axum::Json;
33use export::rest::AppResponseData;
44use session::Session;
55
Modified lib/user/src/handlers/upload_avatar.rs View lib/user/src/handlers/upload_avatar.rs
@@ -1,5 +1,5 @@
1use axum::Json;
12use axum::extract::State;
2use axum::Json;
33use export::rest::AppResponseData;
44use session::Session;
55
Modified lib/user/src/handlers/upsert_push_subscription.rs View lib/user/src/handlers/upsert_push_subscription.rs
@@ -1,5 +1,5 @@
1use axum::Json;
12use axum::extract::State;
2use axum::Json;
33use export::rest::AppResponseData;
44use session::Session;
55
Modified lib/user/src/handlers/verify_add_email.rs View lib/user/src/handlers/verify_add_email.rs
@@ -1,5 +1,5 @@
1use axum::Json;
12use axum::extract::State;
2use axum::Json;
33use export::rest::AppResponseData;
44use session::Session;
55
Modified lib/user/src/services/avatar.rs View lib/user/src/services/avatar.rs
@@ -77,7 +77,11 @@
7777}
7878
7979impl UserService {
80 pub async fn upload_avatar(&self, uid: Uuid, cmd: UploadAvatarCmd) -> Result<AvatarDto, UserError> {
80 pub async fn upload_avatar(
81 &self,
82 uid: Uuid,
83 cmd: UploadAvatarCmd,
84 ) -> Result<AvatarDto, UserError> {
8185 let fmt = AvatarFormat::from_content_type(&cmd.content_type).ok_or_else(|| {
8286 UserError::BadRequest(format!(
8387 "unsupported content_type '{}' (allowed: image/jpeg, image/png, image/webp, image/gif)",
@@ -121,7 +125,7 @@
121125 "bytes": bytes.len(),
122126 "uploaded_at": now,
123127 });
124match serde_json::to_vec(&meta) {
128 match serde_json::to_vec(&meta) {
125129 Ok(meta_bytes) => {
126130 if let Err(error) = self
127131 .storage()
Modified lib/user/src/services/emails.rs View lib/user/src/services/emails.rs
@@ -259,7 +259,11 @@
259259 Ok(updated)
260260 }
261261
262 pub async fn set_primary_email(&self, uid: Uuid, email_id: Uuid) -> Result<EmailDto, UserError> {
262 pub async fn set_primary_email(
263 &self,
264 uid: Uuid,
265 email_id: Uuid,
266 ) -> Result<EmailDto, UserError> {
263267 let txn = self.db().primary();
264268 let updated = txn
265269 .transaction::<_, EmailDto, UserError>(|tx| {
Modified lib/user/src/services/profile.rs View lib/user/src/services/profile.rs
@@ -143,7 +143,8 @@
143143 .filter(space_member_groups::Column::UserId.eq(uid))
144144 .all(self.db().read())
145145 .await?;
146 let group_ids: Vec<Uuid> = member_groups.into_iter().map(|mg| mg.group_id).collect();
146 let group_ids: Vec<Uuid> =
147 member_groups.into_iter().map(|mg| mg.group_id).collect();
147148 let mut granted = if group_ids.is_empty() {
148149 Vec::new()
149150 } else {
@@ -177,7 +178,11 @@
177178 Ok(out)
178179 }
179180
180 pub async fn list_memes(&self, uid: Uuid, limit: Option<u64>) -> Result<Vec<MemeDto>, UserError> {
181 pub async fn list_memes(
182 &self,
183 uid: Uuid,
184 limit: Option<u64>,
185 ) -> Result<Vec<MemeDto>, UserError> {
181186 let lim = limit.unwrap_or(DEFAULT_LIST_LIMIT).clamp(1, 50);
182187 let rows = user_favorite_memes::Entity::find()
183188 .filter(user_favorite_memes::Column::UserId.eq(uid))
Modified lib/user/src/services/service.rs View lib/user/src/services/service.rs
@@ -6,8 +6,8 @@
66use models::users::user;
77use models::users::user_sessions;
88use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
9use session::SessionError;
910use session::share::guard::{SessionGuard, SessionWithUser};
10use session::SessionError;
1111
1212#[derive(Clone)]
1313pub struct UserService {
Modified lib/user/src/services/sessions.rs View lib/user/src/services/sessions.rs
@@ -74,7 +74,11 @@
7474 }
7575
7676 let rows = sel
77 .limit(q.limit.unwrap_or(DEFAULT_PAGE_LIMIT).clamp(1, MAX_PAGE_LIMIT))
77 .limit(
78 q.limit
79 .unwrap_or(DEFAULT_PAGE_LIMIT)
80 .clamp(1, MAX_PAGE_LIMIT),
81 )
7882 .offset(q.offset.unwrap_or(0))
7983 .all(self.db().read())
8084 .await?;
Modified lib/user/src/share/crypto.rs View lib/user/src/share/crypto.rs
@@ -1,7 +1,7 @@
1use base64::Engine;
12use base64::engine::general_purpose::URL_SAFE_NO_PAD;
2use base64::Engine;
3use rand::Rng;
34use rand::rngs::OsRng;
4use rand::Rng;
55use sha2::{Digest, Sha256};
66
77pub fn generate_numeric_code(len: usize) -> String {
Modified lib/user/src/share/mod.rs View lib/user/src/share/mod.rs
@@ -2,4 +2,4 @@
22pub mod guard;
33pub mod page;
44pub mod redis;
5pub mod types;
No newline at end of file
5pub mod types;
Modified lib/user/src/share/page.rs View lib/user/src/share/page.rs
@@ -12,7 +12,9 @@
1212
1313impl PageQuery {
1414 pub fn limit(&self) -> u64 {
15 self.limit.unwrap_or(DEFAULT_PAGE_LIMIT).clamp(1, MAX_PAGE_LIMIT)
15 self.limit
16 .unwrap_or(DEFAULT_PAGE_LIMIT)
17 .clamp(1, MAX_PAGE_LIMIT)
1618 }
1719
1820 pub fn offset(&self) -> u64 {
Modified lib/user/src/share/redis.rs View lib/user/src/share/redis.rs
@@ -63,11 +63,7 @@
6363end
6464return c"#;
6565
66pub async fn incr_expire(
67 redis: &AppRedis,
68 key: &str,
69 ttl_secs: u64,
70) -> Result<i64, RedisError> {
66pub async fn incr_expire(redis: &AppRedis, key: &str, ttl_secs: u64) -> Result<i64, RedisError> {
7167 let mut conn = redis.connection_clone();
7268 dispatch!(conn, |c| {
7369 redis::cmd("EVAL")
Modified lib/user/src/errors.rs View lib/user/src/errors.rs
@@ -1,9 +1,9 @@
11use std::fmt;
22
3use axum::Json;
34use axum::extract::rejection::{PathRejection, QueryRejection};
45use axum::http::StatusCode;
56use axum::response::{IntoResponse, Response};
6use axum::Json;
77use export::rest::{AppResponseData, NoData};
88use utoipa::IntoResponses;
99
Modified lib/user/src/routes.rs View lib/user/src/routes.rs
@@ -1,5 +1,5 @@
1use axum::Router;
12use axum::routing::{delete, get, patch, post};
2use axum::Router;
33
44use crate::handlers::add_email::add_email;
55use crate::handlers::cancel_scheduled_message::cancel_scheduled_message;
@@ -33,8 +33,8 @@
3333use crate::share::guard::user_guard;
3434
3535pub fn router(service: UserService) -> Router<UserService> {
36 let public: Router<UserService> = Router::new()
37 .route("/users/{uid}/profile-detail", get(get_profile_detail));
36 let public: Router<UserService> =
37 Router::new().route("/users/{uid}/profile-detail", get(get_profile_detail));
3838
3939 let protected = Router::new()
4040 .route(
Modified lib/webapi/src/lib.rs View lib/webapi/src/lib.rs
@@ -2,7 +2,7 @@
22
33use axum::Router;
44use export::AppState;
5use session::{store, SessionLayer};
5use session::{SessionLayer, store};
66use tower_http::trace::{DefaultMakeSpan, DefaultOnRequest, DefaultOnResponse, TraceLayer};
77use tracing::Level;
88
@@ -24,10 +24,14 @@
2424 .layer(SessionLayer::new(store))
2525 .layer(
2626 TraceLayer::new_for_http()
27 .make_span_with(DefaultMakeSpan::new().level(Level::INFO).include_headers(false))
27 .make_span_with(
28 DefaultMakeSpan::new()
29 .level(Level::INFO)
30 .include_headers(false),
31 )
2832 .on_request(DefaultOnRequest::new().level(Level::INFO))
2933 .on_response(DefaultOnResponse::new().level(Level::INFO)),
3034 );
3135
3236 Ok(router)
33}
No newline at end of file
37}
Modified lib/webapi/src/openapi.rs View lib/webapi/src/openapi.rs
@@ -12,4 +12,4 @@
1212 doc.merge(user::openapi::UserApiDoc::openapi());
1313 doc.merge(git::openapi::GitApiDoc::openapi());
1414 doc
15}
No newline at end of file
15}
Modified Cargo.lock View Cargo.lock
@@ -596,6 +596,7 @@
596596 "export",
597597 "hkdf",
598598 "hmac 0.13.0",
599 "lc",
599600 "maud",
600601 "models",
601602 "pow-solver",
@@ -2031,11 +2032,13 @@
20312032 "axum",
20322033 "config",
20332034 "email",
2035 "lc",
20342036 "redis",
20352037 "sea-orm",
20362038 "serde",
20372039 "storage",
20382040 "tokio",
2041 "tracing",
20392042 "utoipa",
20402043]
20412044
@@ -2160,6 +2163,12 @@
21602163]
21612164
21622165[[package]]
2166name = "fixedbitset"
2167version = "0.5.7"
2168source = "registry+https://github.com/rust-lang/crates.io-index"
2169checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
2170
2171[[package]]
21632172name = "flate2"
21642173version = "1.1.9"
21652174source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2189,6 +2198,12 @@
21892198
21902199[[package]]
21912200name = "foldhash"
2201version = "0.1.5"
2202source = "registry+https://github.com/rust-lang/crates.io-index"
2203checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
2204
2205[[package]]
2206name = "foldhash"
21922207version = "0.2.0"
21932208source = "registry+https://github.com/rust-lang/crates.io-index"
21942209checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
@@ -3615,6 +3630,25 @@
36153630]
36163631
36173632[[package]]
3633name = "h2"
3634version = "0.4.19"
3635source = "registry+https://github.com/rust-lang/crates.io-index"
3636checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
3637dependencies = [
3638 "atomic-waker",
3639 "bytes",
3640 "fnv",
3641 "futures-core",
3642 "futures-sink",
3643 "http 1.5.0",
3644 "indexmap",
3645 "slab",
3646 "tokio",
3647 "tokio-util",
3648 "tracing",
3649]
3650
3651[[package]]
36183652name = "half"
36193653version = "2.7.1"
36203654source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3668,13 +3702,22 @@
36683702
36693703[[package]]
36703704name = "hashbrown"
3705version = "0.15.5"
3706source = "registry+https://github.com/rust-lang/crates.io-index"
3707checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
3708dependencies = [
3709 "foldhash 0.1.5",
3710]
3711
3712[[package]]
3713name = "hashbrown"
36713714version = "0.16.1"
36723715source = "registry+https://github.com/rust-lang/crates.io-index"
36733716checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
36743717dependencies = [
36753718 "allocator-api2",
36763719 "equivalent",
3677 "foldhash",
3720 "foldhash 0.2.0",
36783721]
36793722
36803723[[package]]
@@ -3685,7 +3728,7 @@
36853728dependencies = [
36863729 "allocator-api2",
36873730 "equivalent",
3688 "foldhash",
3731 "foldhash 0.2.0",
36893732]
36903733
36913734[[package]]
@@ -3878,7 +3921,7 @@
38783921 "futures-channel",
38793922 "futures-core",
38803923 "futures-util",
3881 "h2",
3924 "h2 0.3.27",
38823925 "http 0.2.12",
38833926 "http-body 0.4.6",
38843927 "httparse",
@@ -3902,6 +3945,7 @@
39023945 "bytes",
39033946 "futures-channel",
39043947 "futures-core",
3948 "h2 0.4.19",
39053949 "http 1.5.0",
39063950 "http-body 1.1.0",
39073951 "httparse",
@@ -3910,6 +3954,7 @@
39103954 "pin-project-lite",
39113955 "smallvec",
39123956 "tokio",
3957 "want",
39133958]
39143959
39153960[[package]]
@@ -3924,6 +3969,19 @@
39243969]
39253970
39263971[[package]]
3972name = "hyper-timeout"
3973version = "0.5.2"
3974source = "registry+https://github.com/rust-lang/crates.io-index"
3975checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
3976dependencies = [
3977 "hyper 1.11.0",
3978 "hyper-util",
3979 "pin-project-lite",
3980 "tokio",
3981 "tower-service",
3982]
3983
3984[[package]]
39273985name = "hyper-tls"
39283986version = "0.5.0"
39293987source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3943,12 +4001,17 @@
39434001checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
39444002dependencies = [
39454003 "bytes",
4004 "futures-channel",
4005 "futures-util",
39464006 "http 1.5.0",
39474007 "http-body 1.1.0",
39484008 "hyper 1.11.0",
4009 "libc",
39494010 "pin-project-lite",
4011 "socket2 0.6.5",
39504012 "tokio",
39514013 "tower-service",
4014 "tracing",
39524015]
39534016
39544017[[package]]
@@ -4675,6 +4738,20 @@
46754738]
46764739
46774740[[package]]
4741name = "lc"
4742version = "1.0.0"
4743dependencies = [
4744 "prost",
4745 "serde",
4746 "serde_json",
4747 "thiserror 2.0.20",
4748 "tonic",
4749 "tonic-prost",
4750 "tonic-prost-build",
4751 "tracing",
4752]
4753
4754[[package]]
46784755name = "lettre"
46794756version = "0.11.23"
46804757source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5122,6 +5199,12 @@
51225199]
51235200
51245201[[package]]
5202name = "multimap"
5203version = "0.10.1"
5204source = "registry+https://github.com/rust-lang/crates.io-index"
5205checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
5206
5207[[package]]
51255208name = "native-tls"
51265209version = "0.2.18"
51275210source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5715,6 +5798,17 @@
57155798]
57165799
57175800[[package]]
5801name = "petgraph"
5802version = "0.8.3"
5803source = "registry+https://github.com/rust-lang/crates.io-index"
5804checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
5805dependencies = [
5806 "fixedbitset",
5807 "hashbrown 0.15.5",
5808 "indexmap",
5809]
5810
5811[[package]]
57185812name = "pgvector"
57195813version = "0.4.2"
57205814source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5934,6 +6028,16 @@
59346028]
59356029
59366030[[package]]
6031name = "prettyplease"
6032version = "0.2.37"
6033source = "registry+https://github.com/rust-lang/crates.io-index"
6034checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
6035dependencies = [
6036 "proc-macro2",
6037 "syn 2.0.119",
6038]
6039
6040[[package]]
59376041name = "primefield"
59386042version = "0.14.0"
59396043source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6012,6 +6116,59 @@
60126116]
60136117
60146118[[package]]
6119name = "prost"
6120version = "0.14.4"
6121source = "registry+https://github.com/rust-lang/crates.io-index"
6122checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1"
6123dependencies = [
6124 "bytes",
6125 "prost-derive",
6126]
6127
6128[[package]]
6129name = "prost-build"
6130version = "0.14.4"
6131source = "registry+https://github.com/rust-lang/crates.io-index"
6132checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
6133dependencies = [
6134 "heck 0.5.0",
6135 "itertools 0.14.0",
6136 "log 0.4.33",
6137 "multimap",
6138 "petgraph",
6139 "prettyplease",
6140 "prost",
6141 "prost-types",
6142 "pulldown-cmark",
6143 "pulldown-cmark-to-cmark",
6144 "regex",
6145 "syn 2.0.119",
6146 "tempfile",
6147]
6148
6149[[package]]
6150name = "prost-derive"
6151version = "0.14.4"
6152source = "registry+https://github.com/rust-lang/crates.io-index"
6153checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf"
6154dependencies = [
6155 "anyhow",
6156 "itertools 0.14.0",
6157 "proc-macro2",
6158 "quote",
6159 "syn 2.0.119",
6160]
6161
6162[[package]]
6163name = "prost-types"
6164version = "0.14.4"
6165source = "registry+https://github.com/rust-lang/crates.io-index"
6166checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a"
6167dependencies = [
6168 "prost",
6169]
6170
6171[[package]]
60156172name = "psm"
60166173version = "0.1.32"
60176174source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6060,6 +6217,15 @@
60606217checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
60616218
60626219[[package]]
6220name = "pulldown-cmark-to-cmark"
6221version = "22.0.1"
6222source = "registry+https://github.com/rust-lang/crates.io-index"
6223checksum = "ab1ad36992cead65f02aa399a373a42730922f1525d988172634fdefdecb8a60"
6224dependencies = [
6225 "pulldown-cmark",
6226]
6227
6228[[package]]
60636229name = "quick-xml"
60646230version = "0.28.2"
60656231source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7290,6 +7456,7 @@
72907456 "config",
72917457 "email",
72927458 "export",
7459 "lc",
72937460 "models",
72947461 "sea-orm",
72957462 "serde",
@@ -8091,6 +8258,76 @@
80918258checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
80928259
80938260[[package]]
8261name = "tonic"
8262version = "0.14.6"
8263source = "registry+https://github.com/rust-lang/crates.io-index"
8264checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef"
8265dependencies = [
8266 "async-trait",
8267 "axum",
8268 "base64 0.22.1",
8269 "bytes",
8270 "h2 0.4.19",
8271 "http 1.5.0",
8272 "http-body 1.1.0",
8273 "http-body-util",
8274 "hyper 1.11.0",
8275 "hyper-timeout",
8276 "hyper-util",
8277 "percent-encoding 2.3.2",
8278 "pin-project",
8279 "rustls-native-certs",
8280 "socket2 0.6.5",
8281 "sync_wrapper",
8282 "tokio",
8283 "tokio-rustls",
8284 "tokio-stream",
8285 "tower",
8286 "tower-layer",
8287 "tower-service",
8288 "tracing",
8289]
8290
8291[[package]]
8292name = "tonic-build"
8293version = "0.14.6"
8294source = "registry+https://github.com/rust-lang/crates.io-index"
8295checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322"
8296dependencies = [
8297 "prettyplease",
8298 "proc-macro2",
8299 "quote",
8300 "syn 2.0.119",
8301]
8302
8303[[package]]
8304name = "tonic-prost"
8305version = "0.14.6"
8306source = "registry+https://github.com/rust-lang/crates.io-index"
8307checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0"
8308dependencies = [
8309 "bytes",
8310 "prost",
8311 "tonic",
8312]
8313
8314[[package]]
8315name = "tonic-prost-build"
8316version = "0.14.6"
8317source = "registry+https://github.com/rust-lang/crates.io-index"
8318checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27"
8319dependencies = [
8320 "prettyplease",
8321 "proc-macro2",
8322 "prost-build",
8323 "prost-types",
8324 "quote",
8325 "syn 2.0.119",
8326 "tempfile",
8327 "tonic-build",
8328]
8329
8330[[package]]
80948331name = "tower"
80958332version = "0.5.3"
80968333source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -8098,9 +8335,12 @@
80988335dependencies = [
80998336 "futures-core",
81008337 "futures-util",
8338 "indexmap",
81018339 "pin-project-lite",
8340 "slab",
81028341 "sync_wrapper",
81038342 "tokio",
8343 "tokio-util",
81048344 "tower-layer",
81058345 "tower-service",
81068346 "tracing",
Modified Cargo.toml View Cargo.toml
@@ -14,6 +14,7 @@
1414 "lib/issues",
1515 "lib/git",
1616 "lib/webapi",
17 "lib/lc",
1718 "app/apihost",
1819 "app/githost",
1920 "app/mails",
@@ -44,6 +45,7 @@
4445issues = { path = "lib/issues" }
4546git = { path = "lib/git" }
4647webapi = { path = "lib/webapi" }
48lc = { path = "lib/lc" }
4749
4850
4951
@@ -97,3 +99,8 @@
9799lettre = { version = "0.11", default-features = false, features = ["smtp-transport", "tokio1-rustls", "ring", "rustls-native-certs", "builder"] }
98100anyhow = "1"
99101tracing-subscriber = { version = "0.3", features = ["env-filter"] }
102tonic = "0.14"
103prost = "0.14"
104tonic-prost = "0.14"
105tonic-prost-build = "0.14"
106