Skip to main content

max / makenotwork

Fix Rust 1.95 clippy warnings, bump to 0.5.12 Resolve 88 new clippy lints from Rust 1.95 (collapsible_if with let-chains, unnecessary_map_or, sort_by_key, redundant_closure). No logic changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-10 15:43 UTC
Commit: c402b5b7aefb3050ae564e94b21b1cf4c72a77ae
Parent: 92a8a51
41 files changed, +410 insertions, -421 deletions
@@ -3445,7 +3445,7 @@ dependencies = [
3445 3445
3446 3446 [[package]]
3447 3447 name = "makenotwork"
3448 - version = "0.5.10"
3448 + version = "0.5.11"
3449 3449 dependencies = [
3450 3450 "anyhow",
3451 3451 "argon2",
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makenotwork"
3 - version = "0.5.11"
3 + version = "0.5.12"
4 4 edition = "2024"
5 5 license-file = "LICENSE"
6 6
@@ -528,19 +528,18 @@ fn strip_ansi_escapes(s: &str) -> String {
528 528 let mut chars = s.chars();
529 529 while let Some(c) = chars.next() {
530 530 if c == '\x1b' {
531 - // Consume the '[' and then any parameter/intermediate bytes up to
532 - // the final byte (an ASCII letter).
533 - if let Some(next) = chars.next() {
534 - if next == '[' {
535 - // CSI sequence: skip until we hit a letter (0x40..=0x7E).
536 - for tail in chars.by_ref() {
537 - if tail.is_ascii_alphabetic() {
538 - break;
539 - }
531 + // Consume the next char; if it's '[' we have a CSI sequence
532 + // and we skip parameter/intermediate bytes up to the final byte.
533 + // Otherwise (OSC / other sequences) just drop the two-char escape.
534 + if let Some(next) = chars.next()
535 + && next == '['
536 + {
537 + // CSI sequence: skip until we hit a letter (0x40..=0x7E).
538 + for tail in chars.by_ref() {
539 + if tail.is_ascii_alphabetic() {
540 + break;
540 541 }
541 542 }
542 - // OSC / other sequences starting with ESC but not '[' —
543 - // just drop the two-char escape and continue.
544 543 }
545 544 } else {
546 545 result.push(c);
@@ -149,7 +149,7 @@ pub async fn discover_items(
149 149 ) -> Result<Vec<DbDiscoverItemRow>> {
150 150 let search_term = normalize_search(filters.search);
151 151 let has_search = search_term.is_some();
152 - let short_query = search_term.as_deref().map_or(false, is_short_query);
152 + let short_query = search_term.as_deref().is_some_and(is_short_query);
153 153
154 154 // Build the base query with optional similarity score.
155 155 // For short queries (1-2 chars) use a constant match_score since trigram
@@ -274,7 +274,7 @@ pub async fn count_discover_items(
274 274 ) -> Result<i64> {
275 275 let search_term = normalize_search(filters.search);
276 276 let has_search = search_term.is_some();
277 - let short_query = search_term.as_deref().map_or(false, is_short_query);
277 + let short_query = search_term.as_deref().is_some_and(is_short_query);
278 278
279 279 let mut query = String::from(
280 280 r#"
@@ -316,7 +316,7 @@ pub async fn discover_projects(
316 316 ) -> Result<Vec<DbDiscoverProjectRow>> {
317 317 let search_term = normalize_search(search);
318 318 let has_search = search_term.is_some();
319 - let short_query = search_term.as_deref().map_or(false, is_short_query);
319 + let short_query = search_term.as_deref().is_some_and(is_short_query);
320 320
321 321 let mut query = if has_search && !short_query {
322 322 String::from(
@@ -437,7 +437,7 @@ pub async fn count_discover_projects(
437 437 ) -> Result<i64> {
438 438 let search_term = normalize_search(search);
439 439 let has_search = search_term.is_some();
440 - let short_query = search_term.as_deref().map_or(false, is_short_query);
440 + let short_query = search_term.as_deref().is_some_and(is_short_query);
441 441
442 442 let mut query = String::from(
443 443 r#"
@@ -486,7 +486,7 @@ pub async fn get_item_type_counts(
486 486 ) -> Result<Vec<DbItemTypeCount>> {
487 487 let search_term = normalize_search(search);
488 488 let has_search = search_term.is_some();
489 - let short_query = search_term.as_deref().map_or(false, is_short_query);
489 + let short_query = search_term.as_deref().is_some_and(is_short_query);
490 490
491 491 let mut query = String::from(
492 492 r#"
@@ -552,7 +552,7 @@ pub async fn get_price_range_counts(
552 552 ) -> Result<DbPriceRangeCounts> {
553 553 let search_term = normalize_search(search);
554 554 let has_search = search_term.is_some();
555 - let short_query = search_term.as_deref().map_or(false, is_short_query);
555 + let short_query = search_term.as_deref().is_some_and(is_short_query);
556 556
557 557 let mut query = String::from(
558 558 r#"
@@ -627,7 +627,7 @@ pub async fn get_ai_tier_counts(
627 627 ) -> Result<Vec<DbItemTypeCount>> {
628 628 let search_term = normalize_search(search);
629 629 let has_search = search_term.is_some();
630 - let short_query = search_term.as_deref().map_or(false, is_short_query);
630 + let short_query = search_term.as_deref().is_some_and(is_short_query);
631 631
632 632 let mut query = String::from(
633 633 r#"
@@ -11,6 +11,7 @@ use crate::error::Result;
11 11 ///
12 12 /// Auto-generates a URL-safe slug from the title. If the slug collides with
13 13 /// an existing item in the same project, appends a counter suffix.
14 + #[allow(clippy::too_many_arguments)]
14 15 #[tracing::instrument(skip_all)]
15 16 pub async fn create_item(
16 17 pool: &PgPool,
@@ -31,7 +31,7 @@ pub async fn enqueue_deletions(
31 31 )
32 32 .bind(&s3_keys)
33 33 .bind(&buckets)
34 - .bind(&vec![source; keys.len()])
34 + .bind(vec![source; keys.len()])
35 35 .execute(pool)
36 36 .await?;
37 37 Ok(())
@@ -1016,6 +1016,7 @@ pub async fn cleanup_stale_pending(
1016 1016 /// Create a completed free guest transaction.
1017 1017 ///
1018 1018 /// Returns the number of rows inserted (0 if already claimed via ON CONFLICT).
1019 + #[allow(clippy::too_many_arguments)]
1019 1020 #[tracing::instrument(skip_all)]
1020 1021 pub async fn create_free_guest_transaction(
1021 1022 pool: &PgPool,
@@ -683,6 +683,7 @@ pub async fn get_all_user_emails(pool: &PgPool) -> Result<Vec<(String, Option<St
683 683 }
684 684
685 685 /// Update a user's email notification preferences.
686 + #[allow(clippy::too_many_arguments)]
686 687 #[tracing::instrument(skip_all)]
687 688 pub async fn update_notification_preferences(
688 689 pool: &PgPool,
@@ -28,12 +28,11 @@ pub fn resolve_ref(repo: &Repository, refname: &str) -> Result<Oid, GitError> {
28 28
29 29 // Reject arbitrary revparse expressions (e.g. HEAD~99999, @{upstream}).
30 30 // Only allow simple ref-like names: alphanumeric, dots, hyphens, underscores, slashes.
31 - if refname.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_' | '/')) {
32 - if let Ok(obj) = repo.revparse_single(refname)
33 - && let Ok(commit) = obj.peel(ObjectType::Commit)
34 - {
35 - return Ok(commit.id());
36 - }
31 + if refname.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_' | '/'))
32 + && let Ok(obj) = repo.revparse_single(refname)
33 + && let Ok(commit) = obj.peel(ObjectType::Commit)
34 + {
35 + return Ok(commit.id());
37 36 }
38 37
39 38 Err(GitError::RefNotFound)
@@ -185,7 +185,7 @@ async fn main() {
185 185 ],
186 186 link_prefix: "/docs".to_string(),
187 187 unpublished_pattern: Some("unpublished/".to_string()),
188 - examples_path: Some(std::path::Path::new(&docs_path).join("../examples").into()),
188 + examples_path: Some(std::path::Path::new(&docs_path).join("../examples")),
189 189 },
190 190 ));
191 191
@@ -286,18 +286,18 @@ pub fn snapshot(handle: &PrometheusHandle) -> MetricsSnapshot {
286 286 }
287 287 routes.push((method, path, status, count));
288 288 }
289 - } else if let Some(rest) = line.strip_prefix("http_errors_total{") {
290 - if let Some((labels, value)) = rest.rsplit_once("} ") {
291 - let count: u64 = value.parse().unwrap_or(0);
292 - let kind = extract_label(labels, "kind");
293 - errors.push((kind, count));
294 - }
289 + } else if let Some(rest) = line.strip_prefix("http_errors_total{")
290 + && let Some((labels, value)) = rest.rsplit_once("} ")
291 + {
292 + let count: u64 = value.parse().unwrap_or(0);
293 + let kind = extract_label(labels, "kind");
294 + errors.push((kind, count));
295 295 }
296 296 }
297 297
298 - routes.sort_by(|a, b| b.3.cmp(&a.3));
298 + routes.sort_by_key(|r| std::cmp::Reverse(r.3));
299 299 routes.truncate(20);
300 - errors.sort_by(|a, b| b.1.cmp(&a.1));
300 + errors.sort_by_key(|e| std::cmp::Reverse(e.1));
301 301
302 302 let total_errors = errors.iter().map(|(_, c)| c).sum();
303 303
@@ -207,20 +207,20 @@ pub fn spawn_monitor(
207 207 }
208 208
209 209 // Create WAM ticket on degradation/error transitions
210 - if snap.status != MonitorStatus::Operational {
211 - if let Some(ref wam) = state.wam {
212 - let priority = match snap.status {
213 - MonitorStatus::Error => "critical",
214 - MonitorStatus::Degraded => "high",
215 - MonitorStatus::Operational => unreachable!(),
216 - };
217 - let title = format!("Health status: {}", snap.status.as_str());
218 - let body = format!(
219 - "db: {}\ns3: {}\nsessions: {}\ncheck_ms: {}",
220 - snap.db_ok, snap.s3_ok, snap.sessions_ok, snap.check_duration_ms,
221 - );
222 - wam.create_ticket(&title, Some(&body), priority, "health-status-change", None).await;
223 - }
210 + if snap.status != MonitorStatus::Operational
211 + && let Some(ref wam) = state.wam
212 + {
213 + let priority = match snap.status {
214 + MonitorStatus::Error => "critical",
215 + MonitorStatus::Degraded => "high",
216 + MonitorStatus::Operational => unreachable!(),
217 + };
218 + let title = format!("Health status: {}", snap.status.as_str());
219 + let body = format!(
220 + "db: {}\ns3: {}\nsessions: {}\ncheck_ms: {}",
221 + snap.db_ok, snap.s3_ok, snap.sessions_ok, snap.check_duration_ms,
222 + );
223 + wam.create_ticket(&title, Some(&body), priority, "health-status-change", None).await;
224 224 }
225 225
226 226 previous_status = Some(snap.status);
@@ -235,12 +235,12 @@ pub fn spawn_monitor(
235 235 tracing::warn!(pool_size, active, idle = pool_idle, "DB pool pressure >80%");
236 236 let cooldown_ok = last_pool_alert_at
237 237 .is_none_or(|t| t.elapsed().as_secs() >= constants::ALERT_COOLDOWN_SECS);
238 - if cooldown_ok {
239 - if let Some(ref wam) = state.wam {
240 - let title = format!("DB pool pressure: {active}/{pool_size} active");
241 - wam.create_ticket(&title, None, "high", "db-pool-pressure", None).await;
242 - last_pool_alert_at = Some(Instant::now());
243 - }
238 + if cooldown_ok
239 + && let Some(ref wam) = state.wam
240 + {
241 + let title = format!("DB pool pressure: {active}/{pool_size} active");
242 + wam.create_ticket(&title, None, "high", "db-pool-pressure", None).await;
243 + last_pool_alert_at = Some(Instant::now());
244 244 }
245 245 }
246 246 }
@@ -66,20 +66,19 @@ pub(super) async fn admin_decide_appeal(
66 66 db::users::resolve_appeal(&state.db, user_id, form.decision, response_text).await?;
67 67
68 68 // If approved, resume paused fan subscriptions
69 - if form.decision == db::AppealDecision::Approved {
70 - if let Some(ref stripe) = state.stripe {
71 - if let Some(ref account_id) = db_user.stripe_account_id {
72 - let resumed = db::subscriptions::resume_subscriptions_for_creator(&state.db, user_id).await?;
73 - for sub in &resumed {
74 - if let Err(e) = stripe.resume_subscription(&sub.stripe_subscription_id, account_id).await {
75 - tracing::error!(stripe_sub_id = %sub.stripe_subscription_id, error = ?e, "failed to resume subscription on appeal approval");
76 - }
77 - }
78 - if !resumed.is_empty() {
79 - tracing::info!(user_id = %user_id, resumed = resumed.len(), "resumed fan subscriptions on appeal approval");
80 - }
69 + if form.decision == db::AppealDecision::Approved
70 + && let Some(ref stripe) = state.stripe
71 + && let Some(ref account_id) = db_user.stripe_account_id
72 + {
73 + let resumed = db::subscriptions::resume_subscriptions_for_creator(&state.db, user_id).await?;
74 + for sub in &resumed {
75 + if let Err(e) = stripe.resume_subscription(&sub.stripe_subscription_id, account_id).await {
76 + tracing::error!(stripe_sub_id = %sub.stripe_subscription_id, error = ?e, "failed to resume subscription on appeal approval");
81 77 }
82 78 }
79 + if !resumed.is_empty() {
80 + tracing::info!(user_id = %user_id, resumed = resumed.len(), "resumed fan subscriptions on appeal approval");
81 + }
83 82 }
84 83
85 84 // Send decision email (fire-and-forget)
@@ -164,18 +164,18 @@ pub(super) async fn admin_suspend_user(
164 164 db::moderation::create_action(&state.db, id, admin.id, ModerationActionType::Suspension, reason, None).await?;
165 165
166 166 // Pause fan subscriptions to this creator's projects
167 - if let Some(ref stripe) = state.stripe {
168 - if let Some(ref account_id) = db_user.stripe_account_id {
169 - let subs = db::subscriptions::get_active_subscriptions_by_creator(&state.db, id).await?;
170 - let count = subs.len();
171 - for sub in &subs {
172 - if let Err(e) = stripe.pause_subscription(&sub.stripe_subscription_id, account_id).await {
173 - tracing::error!(stripe_sub_id = %sub.stripe_subscription_id, error = ?e, "failed to pause subscription on Stripe");
174 - }
167 + if let Some(ref stripe) = state.stripe
168 + && let Some(ref account_id) = db_user.stripe_account_id
169 + {
170 + let subs = db::subscriptions::get_active_subscriptions_by_creator(&state.db, id).await?;
171 + let count = subs.len();
172 + for sub in &subs {
173 + if let Err(e) = stripe.pause_subscription(&sub.stripe_subscription_id, account_id).await {
174 + tracing::error!(stripe_sub_id = %sub.stripe_subscription_id, error = ?e, "failed to pause subscription on Stripe");
175 175 }
176 - let paused = db::subscriptions::pause_subscriptions_for_creator(&state.db, id).await?;
177 - tracing::info!(user_id = %id, stripe_paused = count, db_paused = paused, "paused fan subscriptions for suspended creator");
178 176 }
177 + let paused = db::subscriptions::pause_subscriptions_for_creator(&state.db, id).await?;
178 + tracing::info!(user_id = %id, stripe_paused = count, db_paused = paused, "paused fan subscriptions for suspended creator");
179 179 }
180 180
181 181 // Send notification email (fire-and-forget)
@@ -209,16 +209,16 @@ pub(super) async fn admin_unsuspend_user(
209 209 db::moderation::resolve_actions_by_type(&state.db, id, ModerationActionType::Suspension).await?;
210 210
211 211 // Resume paused fan subscriptions
212 - if let Some(ref stripe) = state.stripe {
213 - if let Some(ref account_id) = db_user.stripe_account_id {
214 - let resumed = db::subscriptions::resume_subscriptions_for_creator(&state.db, id).await?;
215 - for sub in &resumed {
216 - if let Err(e) = stripe.resume_subscription(&sub.stripe_subscription_id, account_id).await {
217 - tracing::error!(stripe_sub_id = %sub.stripe_subscription_id, error = ?e, "failed to resume subscription on Stripe");
218 - }
212 + if let Some(ref stripe) = state.stripe
213 + && let Some(ref account_id) = db_user.stripe_account_id
214 + {
215 + let resumed = db::subscriptions::resume_subscriptions_for_creator(&state.db, id).await?;
216 + for sub in &resumed {
217 + if let Err(e) = stripe.resume_subscription(&sub.stripe_subscription_id, account_id).await {
218 + tracing::error!(stripe_sub_id = %sub.stripe_subscription_id, error = ?e, "failed to resume subscription on Stripe");
219 219 }
220 - tracing::info!(user_id = %id, resumed = resumed.len(), "resumed fan subscriptions for unsuspended creator");
221 220 }
221 + tracing::info!(user_id = %id, resumed = resumed.len(), "resumed fan subscriptions for unsuspended creator");
222 222 }
223 223
224 224 tracing::info!(user_id = %id, "admin unsuspended user");
@@ -263,14 +263,14 @@ pub(super) async fn admin_terminate_user(
263 263 ).await?;
264 264
265 265 // Cancel all fan subscriptions — both active and paused (suspension already paused them)
266 - if let Some(ref stripe) = state.stripe {
267 - if let Some(ref account_id) = db_user.stripe_account_id {
268 - let active_subs = db::subscriptions::get_active_subscriptions_by_creator(&state.db, id).await?;
269 - let paused_subs = db::subscriptions::get_paused_subscriptions_by_creator(&state.db, id).await?;
270 - for sub in active_subs.iter().chain(paused_subs.iter()) {
271 - if let Err(e) = stripe.cancel_subscription(&sub.stripe_subscription_id, account_id).await {
272 - tracing::error!(stripe_sub_id = %sub.stripe_subscription_id, error = ?e, "failed to cancel subscription on termination");
273 - }
266 + if let Some(ref stripe) = state.stripe
267 + && let Some(ref account_id) = db_user.stripe_account_id
268 + {
269 + let active_subs = db::subscriptions::get_active_subscriptions_by_creator(&state.db, id).await?;
270 + let paused_subs = db::subscriptions::get_paused_subscriptions_by_creator(&state.db, id).await?;
271 + for sub in active_subs.iter().chain(paused_subs.iter()) {
272 + if let Err(e) = stripe.cancel_subscription(&sub.stripe_subscription_id, account_id).await {
273 + tracing::error!(stripe_sub_id = %sub.stripe_subscription_id, error = ?e, "failed to cancel subscription on termination");
274 274 }
275 275 }
276 276 }
@@ -281,14 +281,14 @@ pub(super) async fn delete_insertion(
281 281 let file_size = insertion.as_ref().map(|i| i.file_size).unwrap_or(0);
282 282
283 283 // Enqueue as a durable safety net
284 - if let Some(ref ins) = insertion {
285 - if let Err(e) = db::pending_s3_deletions::enqueue_deletions(
284 + if let Some(ref ins) = insertion
285 + && let Err(e) = db::pending_s3_deletions::enqueue_deletions(
286 286 &state.db,
287 287 &[(ins.storage_key.clone(), "main".to_string())],
288 288 "insertion_delete",
289 - ).await {
290 - tracing::warn!(error = ?e, "failed to enqueue S3 deletion for insertion");
291 - }
289 + ).await
290 + {
291 + tracing::warn!(error = ?e, "failed to enqueue S3 deletion for insertion");
292 292 }
293 293
294 294 // Optionally delete the S3 object (best-effort)
@@ -58,10 +58,10 @@ pub(in crate::routes::api) async fn export_content(
58 58 let mut files: Vec<(String, String)> = Vec::new();
59 59
60 60 for item in &item_keys {
61 - if let Some(pid) = query.project_id {
62 - if item.project_id != pid {
63 - continue;
64 - }
61 + if let Some(pid) = query.project_id
62 + && item.project_id != pid
63 + {
64 + continue;
65 65 }
66 66 let slug = item.project_slug.as_str();
67 67 let title = sanitize_filename(&item.title);
@@ -80,10 +80,10 @@ pub(in crate::routes::api) async fn export_content(
80 80 }
81 81
82 82 for ver in &version_keys {
83 - if let Some(pid) = query.project_id {
84 - if ver.project_id != pid {
85 - continue;
86 - }
83 + if let Some(pid) = query.project_id
84 + && ver.project_id != pid
85 + {
86 + continue;
87 87 }
88 88 if let Some(ref key) = ver.s3_key {
89 89 let slug = ver.project_slug.as_str();
@@ -224,10 +224,10 @@ pub(super) async fn export_projects(
224 224 }
225 225
226 226 // Include child item IDs for bundles
227 - if item.item_type == db::ItemType::Bundle {
228 - if let Some(child_ids) = bundle_map.get(&item.id) {
229 - item_json["bundle_items"] = serde_json::json!(child_ids);
230 - }
227 + if item.item_type == db::ItemType::Bundle
228 + && let Some(child_ids) = bundle_map.get(&item.id)
229 + {
230 + item_json["bundle_items"] = serde_json::json!(child_ids);
231 231 }
232 232
233 233 items_data.push(item_json);
@@ -73,7 +73,7 @@ pub(super) async fn create_guest_checkout(
73 73 let buyer_amount = body.amount_cents
74 74 .unwrap_or(item.price_cents);
75 75 pricing.validate_amount(buyer_amount)
76 - .map_err(|e| AppError::BadRequest(e))?;
76 + .map_err(AppError::BadRequest)?;
77 77 buyer_amount
78 78 } else {
79 79 item.price_cents
@@ -102,30 +102,30 @@ pub(super) async fn create_guest_checkout(
102 102 if pc.code_purpose == db::CodePurpose::FreeTrial {
103 103 return Err(AppError::BadRequest("Trial codes can only be used for subscriptions".to_string()));
104 104 }
105 - if let Some(starts) = pc.starts_at {
106 - if starts > chrono::Utc::now() {
107 - return Err(AppError::BadRequest("This promo code is not yet active".to_string()));
108 - }
105 + if let Some(starts) = pc.starts_at
106 + && starts > chrono::Utc::now()
107 + {
108 + return Err(AppError::BadRequest("This promo code is not yet active".to_string()));
109 109 }
110 - if let Some(expires) = pc.expires_at {
111 - if expires < chrono::Utc::now() {
112 - return Err(AppError::BadRequest("This promo code has expired".to_string()));
113 - }
110 + if let Some(expires) = pc.expires_at
111 + && expires < chrono::Utc::now()
112 + {
113 + return Err(AppError::BadRequest("This promo code has expired".to_string()));
114 114 }
115 - if let Some(max) = pc.max_uses {
116 - if pc.use_count >= max {
117 - return Err(AppError::BadRequest("This promo code has reached its usage limit".to_string()));
118 - }
115 + if let Some(max) = pc.max_uses
116 + && pc.use_count >= max
117 + {
118 + return Err(AppError::BadRequest("This promo code has reached its usage limit".to_string()));
119 119 }
120 - if let Some(scoped_item) = pc.item_id {
121 - if scoped_item != item_id {
122 - return Err(AppError::BadRequest("This promo code is not valid for this item".to_string()));
123 - }
120 + if let Some(scoped_item) = pc.item_id
121 + && scoped_item != item_id
122 + {
123 + return Err(AppError::BadRequest("This promo code is not valid for this item".to_string()));
124 124 }
125 - if let Some(scoped_project) = pc.project_id {
126 - if item.project_id != scoped_project {
127 - return Err(AppError::BadRequest("This promo code is not valid for this item".to_string()));
128 - }
125 + if let Some(scoped_project) = pc.project_id
126 + && item.project_id != scoped_project
127 + {
128 + return Err(AppError::BadRequest("This promo code is not valid for this item".to_string()));
129 129 }
130 130
131 131 // Apply discount to final price
@@ -237,17 +237,17 @@ pub(super) async fn create_promo_code(
237 237 };
238 238
239 239 // Validate starts_at < expires_at if both present
240 - if let (Some(start), Some(end)) = (starts_at, expires_at) {
241 - if start >= end {
242 - return Err(AppError::BadRequest("Start date must be before expiry date".to_string()));
243 - }
240 + if let (Some(start), Some(end)) = (starts_at, expires_at)
241 + && start >= end
242 + {
243 + return Err(AppError::BadRequest("Start date must be before expiry date".to_string()));
244 244 }
245 245
246 246 // Reject already-expired codes
247 - if let Some(exp) = expires_at {
248 - if exp < chrono::Utc::now() {
249 - return Err(AppError::BadRequest("Expiry date must be in the future".to_string()));
250 - }
247 + if let Some(exp) = expires_at
248 + && exp < chrono::Utc::now()
249 + {
250 + return Err(AppError::BadRequest("Expiry date must be in the future".to_string()));
251 251 }
252 252
253 253 let promo_code = match db::promo_codes::create_promo_code(
@@ -424,7 +424,7 @@ pub(super) async fn update_promo_code(
424 424 ))
425 425 };
426 426
427 - let expires_at = req.expires_at.as_deref().map(|s| parse_date(s)).transpose()?;
427 + let expires_at = req.expires_at.as_deref().map(parse_date).transpose()?;
428 428 let starts_at = req.starts_at.as_deref().map(|s| {
429 429 let s = s.trim();
430 430 if s.is_empty() {
@@ -275,12 +275,11 @@ pub(in crate::routes::api) async fn pause_creator(
275 275
276 276 if let Some(ref stripe) = state.stripe {
277 277 // Cancel the creator's own tier subscription on Stripe (platform-level)
278 - if let Some(ct_sub) = db::creator_tiers::get_creator_sub_by_user(&state.db, user.id).await? {
279 - if ct_sub.status == db::SubscriptionStatus::Active {
280 - if let Err(e) = stripe.cancel_platform_subscription(&ct_sub.stripe_subscription_id).await {
281 - tracing::warn!(error = ?e, "failed to cancel creator tier subscription on Stripe during pause");
282 - }
283 - }
278 + if let Some(ct_sub) = db::creator_tiers::get_creator_sub_by_user(&state.db, user.id).await?
279 + && ct_sub.status == db::SubscriptionStatus::Active
280 + && let Err(e) = stripe.cancel_platform_subscription(&ct_sub.stripe_subscription_id).await
281 + {
282 + tracing::warn!(error = ?e, "failed to cancel creator tier subscription on Stripe during pause");
284 283 }
285 284
286 285 // Set cancel_at_period_end on all active fan subscriptions (connected account)
@@ -475,16 +474,16 @@ pub(in crate::routes::api) async fn submit_appeal(
475 474 let db_user = db::users::get_user_by_id(&state.db, user.id)
476 475 .await?
477 476 .ok_or(AppError::NotFound)?;
478 - if db_user.appeal_decision.as_deref() == Some("denied") {
479 - if let Some(decided_at) = db_user.appeal_decided_at {
480 - let days_since = (chrono::Utc::now() - decided_at).num_days();
481 - if days_since < 30 {
482 - let msg = format!("Your appeal was denied. You may resubmit after {} days.", 30 - days_since);
483 - if is_htmx {
484 - return Ok(AlertTemplate::new("error", &msg).into_response());
485 - }
486 - return Err(AppError::BadRequest(msg));
477 + if db_user.appeal_decision.as_deref() == Some("denied")
478 + && let Some(decided_at) = db_user.appeal_decided_at
479 + {
480 + let days_since = (chrono::Utc::now() - decided_at).num_days();
481 + if days_since < 30 {
482 + let msg = format!("Your appeal was denied. You may resubmit after {} days.", 30 - days_since);
483 + if is_htmx {
484 + return Ok(AlertTemplate::new("error", &msg).into_response());
487 485 }
486 + return Err(AppError::BadRequest(msg));
488 487 }
489 488 }
490 489 // Also reject if an appeal is already pending