Skip to content
Clone

refactor(user): refresh crate across features, handler and metrics

zhenyi committed
parent 3656bd7 commit 4fd870f Browse files

9 files changed

+3 −69

File tree 9
Modified lib/user/features/avatar/types.rs View lib/user/features/avatar/types.rs
@@ -75,10 +75,8 @@
7575
7676#[derive(Debug, Clone, Deserialize, ToSchema)]
7777pub struct UploadAvatarCmd {
78 /// MIME type, e.g. `image/png`.
79 pub content_type: String,
80 /// Base64 (standard alphabet, with optional padding) encoded image bytes.
81 pub data_base64: String,
78 pub content_type: String,
79 pub data_base64: String,
8280}
8381
8482pub fn decode_upload(cmd: &UploadAvatarCmd) -> UserResult<(AvatarFormat, Vec<u8>)> {
Modified lib/user/features/notifications/usecase.rs View lib/user/features/notifications/usecase.rs
@@ -17,7 +17,6 @@
1717 UnreadCountResp, UpdateReportCmd,
1818};
1919
20// === notifications ===
2120
2221pub async fn list_notifications(
2322 svc: &UserService,
@@ -40,7 +39,6 @@
4039 sel = sel.filter(db::user::user_notifications::COLUMN.read_at.is_null());
4140 }
4241 if let Some(_nt) = q.notification_type {
43 // Caller-side filter could be added; mapping to db enum omitted here.
4442 }
4543 let rows = sel.all(&db).await?;
4644 Ok(rows.into_iter().map(NotificationView::from).collect())
@@ -152,7 +150,6 @@
152150 result
153151}
154152
155// === recent mentions ===
156153
157154pub async fn list_recent_mentions(
158155 svc: &UserService,
@@ -183,7 +180,6 @@
183180 result
184181}
185182
186// === reports ===
187183
188184pub async fn submit_report(
189185 svc: &UserService,
@@ -282,7 +278,6 @@
282278 result
283279}
284280
285// === saved messages ===
286281
287282pub async fn save_message(
288283 svc: &UserService,
@@ -378,7 +373,6 @@
378373 result
379374}
380375
381// === scheduled messages ===
382376
383377pub async fn list_scheduled(
384378 svc: &UserService,
Modified lib/user/features/security/usecase.rs View lib/user/features/security/usecase.rs
@@ -31,7 +31,6 @@
3131 out.iter().map(|b| format!("{:02x}", b)).collect()
3232}
3333
34// === recovery codes ===
3534
3635pub async fn list_recovery(svc: &UserService, caller: Uuid) -> UserResult<Vec<RecoveryCodeView>> {
3736 let _timer = metrics::Timer::start(metrics::FEATURE_RECOVERY_CODES_LIST);
@@ -97,7 +96,6 @@
9796 result
9897}
9998
100// === access keys ===
10199
102100pub async fn list_access_keys(
103101 svc: &UserService,
@@ -200,7 +198,6 @@
200198 result
201199}
202200
203// === ssh keys ===
204201
205202pub async fn list_ssh_keys(svc: &UserService, caller: Uuid) -> UserResult<Vec<SshKeyView>> {
206203 let _timer = metrics::Timer::start(metrics::FEATURE_SSH_KEYS_LIST);
@@ -319,7 +316,6 @@
319316 }
320317}
321318
322// === push subscriptions ===
323319
324320pub async fn list_push(svc: &UserService, caller: Uuid) -> UserResult<Vec<PushSubscriptionView>> {
325321 let _timer = metrics::Timer::start(metrics::FEATURE_PUSH_SUBS_LIST);
Modified lib/user/features/social/usecase.rs View lib/user/features/social/usecase.rs
@@ -52,12 +52,6 @@
5252 metrics::record_outcome(metrics::FEATURE_SOCIAL_FOLLOW, &result);
5353 match &result {
5454 Ok(()) => {
55 // Distinguish "created" from "already following" via the inner signal.
56 // We re-check the call by inspecting the original request shape; the
57 // simple rule: if the call entered the "already-existing" branch we
58 // still call record_outcome above. To split, the call site already
59 // records the high-cardinality-neutral outcome. Use the helper for
60 // the most informative label.
6155 record_social_follow(SocialFollowOutcome::Created);
6256 }
6357 Err(UserError::BadRequest(_)) => {
Modified lib/user/features/suspensions/usecase.rs View lib/user/features/suspensions/usecase.rs
@@ -70,8 +70,6 @@
7070 user_active.updated_at = Set(now);
7171 user_active.update(&txn).await?;
7272
73 // B4: revoke all refresh sessions atomically with the suspension so a
74 // suspended user cannot refresh to keep the session alive.
7573 jwt::session::delete_sessions_by_user(&txn, target)
7674 .await
7775 .map_err(|e| UserError::Internal(format!("failed to revoke sessions: {e}")))?;
@@ -79,9 +77,6 @@
7977 txn.commit().await?;
8078
8179 // VULN-002/A3: a suspension must take effect immediately — deny-list the
82 // user's live access tokens so they stop validating without waiting for
83 // expiry. (Refresh-session revocation is handled alongside the refresh
84 // gate; see the refresh use-case.)
8580 let ttl = std::cmp::max(svc.jwt.settings.access_ttl.whole_seconds(), 1) as u64;
8681 rate_limit::AccessTokenBlacklist::new(svc.kv.clone())
8782 .revoke_user(target, ttl)
Modified lib/user/handler/middleware/metrics.rs View lib/user/handler/middleware/metrics.rs
@@ -2,12 +2,6 @@
22use axum::middleware::Next;
33use axum::response::Response;
44
5/// HTTP-level metrics layer for the `user` crate.
6///
7/// Emits `user_http_requests_total{route,method,status_class}` (counter) and
8/// `user_http_request_duration_seconds{route,method,status_class}` (histogram)
9/// on every request that traverses the router. Uses `MatchedPath` so the
10/// `route` label stays low-cardinality (no user-id path params).
115pub async fn metrics_layer(req: Request, next: Next) -> Response {
126 crate::metrics::ensure_described();
137
Modified lib/user/handler/openapi.rs View lib/user/handler/openapi.rs
@@ -45,11 +45,9 @@
4545 (name = "user", description = "User profile, account, sessions and notifications endpoints.")
4646 ),
4747 paths(
48 // === avatar ===
4948 crate::handler::avatar::current::handler,
5049 crate::handler::avatar::upload::handler,
5150 crate::handler::avatar::delete::handler,
52 // === profile ===
5351 crate::handler::profile::me::handler,
5452 crate::handler::profile::public_view::handler,
5553 crate::handler::profile::update_profile::handler,
@@ -58,10 +56,8 @@
5856 crate::handler::profile::memes::list_handler,
5957 crate::handler::profile::memes::create_handler,
6058 crate::handler::profile::memes::delete_handler,
61 // === preferences ===
6259 crate::handler::preferences::get::handler,
6360 crate::handler::preferences::update::handler,
64 // === social ===
6561 crate::handler::social::follow::handler,
6662 crate::handler::social::unfollow::handler,
6763 crate::handler::social::list_followers::handler,
@@ -69,15 +65,12 @@
6965 crate::handler::social::block::handler,
7066 crate::handler::social::unblock::handler,
7167 crate::handler::social::list_blocks::handler,
72 // === devices ===
7368 crate::handler::devices::list_devices::handler,
7469 crate::handler::devices::update_device::handler,
7570 crate::handler::devices::delete_device::handler,
76 // === sessions ===
7771 crate::handler::sessions::list_sessions::handler,
7872 crate::handler::sessions::revoke_session::handler,
7973 crate::handler::sessions::revoke_sessions::handler,
80 // === emails ===
8174 crate::handler::emails::list_emails::handler,
8275 crate::handler::emails::add_email::handler,
8376 crate::handler::emails::set_primary::handler,
@@ -85,19 +78,16 @@
8578 crate::handler::emails::request_change::handler,
8679 crate::handler::emails::confirm_change::handler,
8780 crate::handler::emails::cancel_change::handler,
88 // === deletion ===
8981 crate::handler::deletion::request::handler,
9082 crate::handler::deletion::cancel::handler,
9183 crate::handler::deletion::confirm::handler,
9284 crate::handler::deletion::list_pending::handler,
9385 crate::handler::deletion::complete::handler,
94 // === suspensions ===
9586 crate::handler::suspensions::create::handler,
9687 crate::handler::suspensions::revoke::handler,
9788 crate::handler::suspensions::list_user::handler,
9889 crate::handler::suspensions::list_all::handler,
9990 crate::handler::suspensions::list_self::handler,
100 // === security ===
10191 crate::handler::security::recovery_codes::list::handler,
10292 crate::handler::security::recovery_codes::regenerate::handler,
10393 crate::handler::security::access_keys::list_keys::handler,
@@ -109,7 +99,6 @@
10999 crate::handler::security::push_subscriptions::list::handler,
110100 crate::handler::security::push_subscriptions::upsert::handler,
111101 crate::handler::security::push_subscriptions::delete::handler,
112 // === notifications ===
113102 crate::handler::notifications::list_notifications::handler,
114103 crate::handler::notifications::mark_read::handler,
115104 crate::handler::notifications::mark_all_read::handler,
Modified lib/user/error.rs View lib/user/error.rs
@@ -82,9 +82,7 @@
8282 )
8383 }
8484
85 /// Low-cardinality label for metrics emission.
86 /// Bounded vocabulary; never derives from the error message body.
87 pub fn error_kind(&self) -> &'static str {
85 pub fn error_kind(&self) -> &'static str {
8886 match self {
8987 UserError::BadRequest(_) => "bad_request",
9088 UserError::Unauthorized(_) => "unauthorized",
Modified lib/user/metrics.rs View lib/user/metrics.rs
@@ -1,16 +1,9 @@
1//! Tracing layer for the `user` crate.
2//!
3//! Emits metrics through the `metrics` facade. No exporter is installed here;
4//! the host binary is responsible for binding a recorder. All labels are
5//! bounded to keep cardinality low (no `uid`/`email`/`ip`/`jti`).
6
71use std::time::Instant;
82
93use metrics::{counter, describe_counter, describe_histogram, histogram};
104
115use crate::error::UserError;
126
13// ── Feature constants (one per usecase) ───────────────────────────────────
147
158pub const FEATURE_PROFILE_ME: &str = "profile_me";
169pub const FEATURE_PROFILE_UPDATE: &str = "profile_update";
@@ -89,12 +82,10 @@
8982pub const FEATURE_PREFERENCES_GET: &str = "preferences_get";
9083pub const FEATURE_PREFERENCES_UPDATE: &str = "preferences_update";
9184
92// ── Outcome labels (bounded enums) ────────────────────────────────────────
9385
9486pub const OUTCOME_SUCCESS: &str = "success";
9587pub const OUTCOME_ERROR: &str = "error";
9688
97// ── Metric description registration (Once) ────────────────────────────────
9889
9990static DESCRIBE_ONCE: std::sync::Once = std::sync::Once::new();
10091
@@ -131,9 +122,7 @@
131122 );
132123}
133124
134// ── Timer (RAII) ──────────────────────────────────────────────────────────
135125
136/// Records the usecase execution duration on drop.
137126pub struct Timer {
138127 pub feature: &'static str,
139128 pub start: Instant,
@@ -159,7 +148,6 @@
159148
160149impl Drop for Timer {
161150 fn drop(&mut self) {
162 // Avoid double-counting: only record if not yet consumed via `observe`.
163151 let elapsed = self.start.elapsed().as_secs_f64();
164152 histogram!(
165153 "user_usecase_duration_seconds",
@@ -169,10 +157,7 @@
169157 }
170158}
171159
172// ── Generic outcome counter ───────────────────────────────────────────────
173160
174/// Bump invocation counter (always) and, on `Err`, an error counter labeled
175/// by `UserError::error_kind()`. Drop semantics of `_timer` capture latency.
176161pub fn record_outcome<T>(feature: &'static str, result: &Result<T, UserError>) {
177162 match result {
178163 Ok(_) => {
@@ -201,7 +186,6 @@
201186 }
202187}
203188
204// ── Avatar helpers ────────────────────────────────────────────────────────
205189
206190#[derive(Debug, Clone, Copy)]
207191pub enum AvatarFormat {
@@ -246,7 +230,6 @@
246230 .increment(1);
247231}
248232
249// ── Social helpers ────────────────────────────────────────────────────────
250233
251234#[derive(Debug, Clone, Copy)]
252235pub enum SocialFollowOutcome {
@@ -302,7 +285,6 @@
302285 .increment(1);
303286}
304287
305// ── Email helpers ─────────────────────────────────────────────────────────
306288
307289#[derive(Debug, Clone, Copy)]
308290pub enum EmailOp {
@@ -365,7 +347,6 @@
365347 .increment(1);
366348}
367349
368// ── Security helpers ──────────────────────────────────────────────────────
369350
370351#[derive(Debug, Clone, Copy)]
371352pub enum SecurityResourceOp {
@@ -418,7 +399,6 @@
418399 .increment(1);
419400}
420401
421// ── Notification helpers ──────────────────────────────────────────────────
422402
423403#[derive(Debug, Clone, Copy)]
424404pub enum NotificationOp {
@@ -503,7 +483,6 @@
503483 .increment(1);
504484}
505485
506// ── Suspension helpers ────────────────────────────────────────────────────
507486
508487#[derive(Debug, Clone, Copy)]
509488pub enum SuspensionOp {
@@ -556,7 +535,6 @@
556535 .increment(1);
557536}
558537
559// ── Deletion helpers ──────────────────────────────────────────────────────
560538
561539#[derive(Debug, Clone, Copy)]
562540pub enum DeletionOp {
@@ -609,7 +587,6 @@
609587 .increment(1);
610588}
611589
612// ── Preferences helpers ───────────────────────────────────────────────────
613590
614591#[derive(Debug, Clone, Copy)]
615592pub enum PreferencesOp {
@@ -635,7 +612,6 @@
635612 .increment(1);
636613}
637614
638// ── Storage sub-counter ───────────────────────────────────────────────────
639615
640616pub fn record_storage_operation(op: &'static str, kind: &'static str, outcome: &'static str) {
641617 counter!(