Skip to main content

Stripe/McMaster-Carr quality remediations Tier 1 - Error fidelity: ResultExt trait for error context chains, structured error logging, user_id in request spans, context on payment/auth/S3 paths Tier 2 - Observability: Prometheus metrics (/metrics endpoint, request counters, duration histograms, error counters, DB pool gauges), Grafana+Prometheus on Hetzner, admin metrics dashboard, resource IDs in handler spans, rate limit response headers (X-RateLimit-*) Tier 3 - API discipline: API versioning (/api/v1/ for SyncKit, license keys, OTA, public), MNW-Version response header, idempotency keys (table + middleware), webhook retry queue (table + exponential backoff + scheduler worker) Tier 4 - Testability: EmailTransport trait + PostmarkTransport, PaymentProvider trait, MockEmailTransport + MockPaymentProvider, TestHarness::with_mocks(), 6 new integration tests (checkout flow, email assertions, failure modes) Tier 5 - Database resilience: Pool health (test_before_acquire, max_lifetime, idle_timeout, min_connections), slow query logging (100ms WARN threshold), index coverage audit (all hot paths verified) Tier 6 - Performance: Cache-Control middleware (CDN caching for public pages, no-cache for dashboard, no-store for APIs), page weight audit (31KB gzipped total) Type safety: Visibility, ProjectRole, SubscriptionStatus enums replacing strings, PriceCents newtype, impl_str_enum! macro enhanced with PartialEq<str>

Author: Max J. <87768334+MaxJMath@users.noreply.github.com> - 2026-04-23 19:16 UTC

Commit:

b3f80f618a6724bd6e816bd408990cecd348418b

Parent:

fffc449

85 files changed,

+2501 insertions,

-372 deletions

OldNewLine
@@ -28,6 +28,18 @@
28
28
"cpufeatures",
29
29
]
30
30
31
[[package]]
32
name = "ahash"
33
version = "0.8.12"
34
source = "registry+https://github.com/rust-lang/crates.io-index"
35
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
36
dependencies = [
37
"cfg-if",
38
"once_cell",
39
"version_check",
40
"zerocopy",
41
]
42
31
43
[[package]]
32
44
name = "aho-corasick"
33
45
version = "1.1.4"
@@ -3373,7 +3385,7 @@
3373
3385
3374
3386
[[package]]
3375
3387
name = "makenotwork"
3376
version = "0.3.25"
3388
version = "0.3.26"
3377
3389
dependencies = [
3378
3390
"anyhow",
3379
3391
"argon2",
@@ -3397,6 +3409,9 @@
3397
3409
"http-body-util",
3398
3410
"infer 0.19.0",
3399
3411
"jsonwebtoken",
3412
"log",
3413
"metrics",
3414
"metrics-exporter-prometheus",
3400
3415
"openssl",
3401
3416
"rand 0.8.5",
3402
3417
"regex",
@@ -3518,6 +3533,46 @@
3518
3533
"libc",
3519
3534
]
3520
3535
3536
[[package]]
3537
name = "metrics"
3538
version = "0.24.3"
3539
source = "registry+https://github.com/rust-lang/crates.io-index"
3540
checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8"
3541
dependencies = [
3542
"ahash",
3543
"portable-atomic",
3544
]
3545
3546
[[package]]
3547
name = "metrics-exporter-prometheus"
3548
version = "0.18.1"
3549
source = "registry+https://github.com/rust-lang/crates.io-index"
3550
checksum = "3589659543c04c7dc5526ec858591015b87cd8746583b51b48ef4353f99dbcda"
3551
dependencies = [
3552
"base64 0.22.1",
3553
"indexmap",
3554
"metrics",
3555
"metrics-util",
3556
"quanta",
3557
"thiserror 2.0.18",
3558
]
3559
3560
[[package]]
3561
name = "metrics-util"
3562
version = "0.20.1"
3563
source = "registry+https://github.com/rust-lang/crates.io-index"
3564
checksum = "cdfb1365fea27e6dd9dc1dbc19f570198bc86914533ad639dae939635f096be4"
3565
dependencies = [
3566
"crossbeam-epoch",
3567
"crossbeam-utils",
3568
"hashbrown 0.16.1",
3569
"metrics",
3570
"quanta",
3571
"rand 0.9.2",
3572
"rand_xoshiro",
3573
"sketches-ddsketch",
3574
]
3575
3521
3576
[[package]]
3522
3577
name = "mime"
3523
3578
version = "0.3.17"
@@ -4442,6 +4497,15 @@
4442
4497
"rand_core 0.5.1",
4443
4498
]
4444
4499
4500
[[package]]
4501
name = "rand_xoshiro"
4502
version = "0.7.0"
4503
source = "registry+https://github.com/rust-lang/crates.io-index"
4504
checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41"
4505
dependencies = [
4506
"rand_core 0.9.5",
4507
]
4508
4445
4509
[[package]]
4446
4510
name = "raw-cpuid"
4447
4511
version = "11.6.0"
@@ -5150,6 +5214,12 @@
5150
5214
source = "registry+https://github.com/rust-lang/crates.io-index"
5151
5215
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
5152
5216
5217
[[package]]
5218
name = "sketches-ddsketch"
5219
version = "0.3.1"
5220
source = "registry+https://github.com/rust-lang/crates.io-index"
5221
checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b"
5222
5153
5223
[[package]]
5154
5224
name = "slab"
5155
5225
version = "0.4.12"
OldNewLine
@@ -76,10 +76,17 @@
76
76
# CLI
77
77
clap = { version = "4", features = ["derive"] }
78
78
79
# Logging (used by sqlx slow query config)
80
log = "0.4"
81
79
82
# Error handling
80
83
thiserror = "2.0.18"
81
84
anyhow = "1.0.101"
82
85
86
# Metrics
87
metrics = "0.24"
88
metrics-exporter-prometheus = { version = "0.18.1", default-features = false }
89
83
90
# Markdown rendering + documentation engine
84
91
docengine = { path = "../shared/docengine", features = ["doc-loader", "directives", "frontmatter", "media-urls"] }
85
92
OldNewLine
@@ -147,6 +147,10 @@
147
147
}
148
148
}
149
149
150
// Record user_id in the current span so all downstream logs
151
// (DB queries, error handlers, etc.) include it automatically.
152
tracing::Span::current().record("user_id", tracing::field::display(&user.id));
153
150
154
Ok(AuthUser(user))
151
155
}
152
156
}
OldNewLine
@@ -6,7 +6,12 @@
6
6
7
7
// -- Database --
8
8
pub const DB_POOL_MAX_CONNECTIONS: u32 = 25;
9
pub const DB_POOL_MIN_CONNECTIONS: u32 = 2;
9
10
pub const DB_ACQUIRE_TIMEOUT_SECS: u64 = 3;
11
/// Rotate connections after 30 minutes to prevent stale sessions.
12
pub const DB_MAX_LIFETIME_SECS: u64 = 1800;
13
/// Prune idle connections after 10 minutes.
14
pub const DB_IDLE_TIMEOUT_SECS: u64 = 600;
10
15
11
16
// -- Sessions --
12
17
pub const SESSION_EXPIRY_DAYS: i64 = 7;
OldNewLine
@@ -115,19 +115,25 @@
115
115
let status = self.status_code();
116
116
let message = self.user_message();
117
117
118
// Log internal errors
118
// Increment error counter for Prometheus
119
metrics::counter!("http_errors_total", "kind" => self.tag()).increment(1);
120
121
// Log server errors with structured fields.
122
// The request_id and user_id are already in the parent tracing span
123
// (set by TraceLayer and AuthUser respectively), so they appear
124
// automatically in these log lines.
119
125
match &self {
120
126
AppError::Database(e) => {
121
tracing::error!("Database error: {:?}", e);
127
tracing::error!(error.kind = "database", error.detail = ?e, "request failed");
122
128
}
123
129
AppError::Internal(e) => {
124
tracing::error!("Internal error: {:?}", e);
130
tracing::error!(error.kind = "internal", error.detail = ?e, "request failed");
125
131
}
126
132
AppError::Storage(e) => {
127
tracing::error!("Storage error: {:?}", e);
133
tracing::error!(error.kind = "storage", error.detail = %e, "request failed");
128
134
}
129
135
AppError::MalwareDetected(detail) => {
130
tracing::warn!("File quarantined: {}", detail);
136
tracing::warn!(error.kind = "malware_detected", error.detail = %detail, "file quarantined");
131
137
}
132
138
_ => {}
133
139
}
@@ -167,6 +173,34 @@
167
173
/// Result type alias for handlers
168
174
pub type Result<T> = std::result::Result<T, AppError>;
169
175
176
/// Extension trait for adding context to any `Result<T, E>` where `E` can
177
/// convert into `AppError`. The context string is preserved in the error chain
178
/// via `anyhow::Context`, making it visible in structured error logs.
179
///
180
/// ```ignore
181
/// use crate::error::ResultExt;
182
/// let user = db::users::get_user_by_id(&db, id)
183
/// .await
184
/// .context("fetch user for checkout")?;
185
/// ```
186
pub trait ResultExt<T> {
187
fn context(self, msg: &'static str) -> Result<T>;
188
fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T>;
189
}
190
191
impl<T, E> ResultExt<T> for std::result::Result<T, E>
192
where
193
E: std::error::Error + Send + Sync + 'static,
194
{
195
fn context(self, msg: &'static str) -> Result<T> {
196
self.map_err(|e| AppError::Internal(anyhow::Error::new(e).context(msg)))
197
}
198
199
fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T> {
200
self.map_err(|e| AppError::Internal(anyhow::Error::new(e).context(f())))
201
}
202
}
203
170
204
#[cfg(test)]
171
205
mod tests {
172
206
use super::*;
OldNewLine
@@ -169,13 +169,14 @@
169
169
}
170
170
171
171
/// Build a rate limiter config from a per-millisecond interval and burst size.
172
/// Includes `x-ratelimit-limit`, `x-ratelimit-remaining`, and `retry-after` headers.
172
173
pub fn rate_limiter_ms(
173
174
ms: u64,
174
175
burst: u32,
175
176
) -> std::sync::Arc<
176
177
tower_governor::governor::GovernorConfig<
177
178
tower_governor::key_extractor::SmartIpKeyExtractor,
178
::governor::middleware::NoOpMiddleware,
179
::governor::middleware::StateInformationMiddleware,
179
180
>,
180
181
> {
181
182
std::sync::Arc::new(
@@ -183,19 +184,21 @@
183
184
.key_extractor(tower_governor::key_extractor::SmartIpKeyExtractor)
184
185
.per_millisecond(ms)
185
186
.burst_size(burst)
187
.use_headers()
186
188
.finish()
187
189
.expect("rate limiter config"),
188
190
)
189
191
}
190
192
191
193
/// Build a rate limiter config from a per-second rate and burst size.
194
/// Includes `x-ratelimit-limit`, `x-ratelimit-remaining`, and `retry-after` headers.
192
195
pub fn rate_limiter_per_sec(
193
196
per_sec: u64,
194
197
burst: u32,
195
198
) -> std::sync::Arc<
196
199
tower_governor::governor::GovernorConfig<
197
200
tower_governor::key_extractor::SmartIpKeyExtractor,
198
::governor::middleware::NoOpMiddleware,
201
::governor::middleware::StateInformationMiddleware,
199
202
>,
200
203
> {
201
204
std::sync::Arc::new(
@@ -203,6 +206,7 @@
203
206
.key_extractor(tower_governor::key_extractor::SmartIpKeyExtractor)
204
207
.per_second(per_sec)
205
208
.burst_size(burst)
209
.use_headers()
206
210
.finish()
207
211
.expect("rate limiter config"),
208
212
)
OldNewLine
@@ -13,6 +13,7 @@
13
13
pub mod helpers;
14
14
pub mod import;
15
15
pub mod markdown;
16
pub mod metrics;
16
17
pub mod monitor;
17
18
pub mod mt_client;
18
19
pub mod payments;
@@ -44,7 +45,7 @@
44
45
use config::Config;
45
46
use docengine::DocLoader;
46
47
use email::EmailClient;
47
use payments::StripeClient;
48
use payments::PaymentProvider;
48
49
use routes::{
49
50
admin_routes, api_routes, auth_routes, build_routes, git_routes, git_issue_routes,
50
51
oauth_routes, ota_routes, page_routes, postmark_routes, storage_routes, stripe_routes,
@@ -61,7 +62,7 @@
61
62
pub config: Config,
62
63
pub s3: Option<Arc<dyn StorageBackend>>,
63
64
pub synckit_s3: Option<Arc<dyn StorageBackend>>,
64
pub stripe: Option<StripeClient>,
65
pub stripe: Option<Arc<dyn PaymentProvider>>,
65
66
pub email: EmailClient,
66
67
pub docs: Arc<DocLoader>,
67
68
pub scanner: Option<Arc<ScanPipeline>>,
@@ -83,6 +84,8 @@
83
84
/// SSE push notification channels for SyncKit subscribers.
84
85
/// Key: (app_id, user_id), Value: broadcast sender that SSE connections subscribe to.
85
86
pub sync_notify: Arc<DashMap<(SyncAppId, UserId), tokio::sync::broadcast::Sender<()>>>,
87
/// Prometheus metrics handle for rendering the admin dashboard. `None` in tests.
88
pub metrics_handle: Option<metrics_exporter_prometheus::PrometheusHandle>,
86
89
}
87
90
88
91
impl AppState {
@@ -102,8 +105,12 @@
102
105
}
103
106
104
107
/// Build the app router with all routes and middleware (minus tracing/TCP).
105
pub fn build_app(state: AppState, session_layer: SessionManagerLayer<PostgresStore>) -> Router {
106
Router::new()
108
pub fn build_app(
109
state: AppState,
110
session_layer: SessionManagerLayer<PostgresStore>,
111
) -> Router {
112
let metrics_handle = state.metrics_handle.clone();
113
let mut app = Router::new()
107
114
.merge(page_routes())
108
115
.merge(auth_routes())
109
116
.merge(api_routes())
@@ -140,8 +147,22 @@
140
147
.service(ServeDir::new("rustdoc")),
141
148
)
142
149
.fallback(routes::custom_domain::custom_domain_fallback)
143
.with_state(state)
150
.with_state(state.clone());
151
152
// /metrics endpoint (Prometheus scrape target). Only available when the
153
// recorder is installed (i.e. in the real server, not in integration tests).
154
if let Some(handle) = metrics_handle {
155
app = app.merge(
156
Router::new()
157
.route("/metrics", axum::routing::get(metrics::render))
158
.with_state(handle),
159
);
160
}
161
162
app.layer(middleware::from_fn(metrics::cache_control_middleware))
163
.layer(middleware::from_fn(metrics::metrics_middleware))
144
164
.layer(middleware::from_fn(csrf::csrf_middleware))
165
.layer(middleware::from_fn_with_state(state.clone(), metrics::idempotency_middleware))
145
166
.layer(session_layer)
146
167
.layer(RequestBodyLimitLayer::new(1024 * 1024))
147
168
}