Surface real B2 upload errors and enforce 10MB body limit on document uploads
All checks were successful
build-and-release / build (developers) (push) Successful in 1m27s
build-and-release / build (employees) (push) Successful in 1m52s
build-and-release / build (customers) (push) Successful in 1m56s
build-and-release / build (cron) (push) Successful in 2m16s
build-and-release / build (companies) (push) Successful in 2m21s
build-and-release / build (catering-services) (push) Successful in 2m29s
build-and-release / build (gateway) (push) Successful in 39s
build-and-release / build (fitness-trainers) (push) Successful in 2m0s
build-and-release / build (graphic-designers) (push) Successful in 1m52s
build-and-release / build (jobs) (push) Successful in 1m34s
build-and-release / build (job-seekers) (push) Successful in 2m4s
build-and-release / build (photographers) (push) Successful in 1m35s
build-and-release / build (makeup-artists) (push) Successful in 2m41s
build-and-release / build (payments) (push) Successful in 3m15s
build-and-release / build (tutors) (push) Successful in 2m16s
build-and-release / build (social-media-managers) (push) Successful in 2m33s
build-and-release / build (ugc-content-creators) (push) Successful in 2m47s
build-and-release / build (video-editors) (push) Successful in 2m32s
build-and-release / build (users) (push) Successful in 4m11s

Document upload endpoints (job_seekers, customers, companies, and the
profession_shared crate used by 10 profession apps) returned a generic
"File upload failed" 500 on any storage error, hiding the actual cause
from both the API response and (for job_seekers/companies) the server
logs, which only printed anyhow's outer context via Display instead of
the full error chain via Debug.

Also add an explicit DefaultBodyLimit(11MB) to every affected app's
router — none had one, so axum's implicit 2MB default could silently
reject uploads under the UI's advertised 10MB cap.
This commit is contained in:
Ashwin Kumar Sivakumar 2026-07-26 19:25:53 +05:30
parent da8188563f
commit cd695514fe
17 changed files with 32 additions and 19 deletions

View file

@ -5,7 +5,7 @@
mod handlers;
mod admin;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@ -42,6 +42,7 @@ async fn main() {
.nest("/api/catering-services", handlers::router())
.nest("/api/admin/catering-services", admin::router())
.route("/health", get(|| async { "Catering Services OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -605,8 +605,8 @@ async fn upload_documents(
{
Ok(u) => u,
Err(e) => {
tracing::error!("B2 upload failed for company {}: {}", company.id, e);
return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "File upload failed" }))).into_response();
tracing::error!("B2 upload failed for company {}: {:?}", company.id, e);
return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "storage_upload_failed", "detail": format!("{:?}", e) }))).into_response();
}
};

View file

@ -4,7 +4,7 @@
mod handlers;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use cache::RedisPool;
use std::net::SocketAddr;
use std::sync::Arc;
@ -51,6 +51,7 @@ async fn main() {
.nest("/api/admin/companies", handlers::admin::router())
.route("/health", get(|| async { "Companies OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -136,7 +136,7 @@ async fn upload_document(
Ok(url) => (StatusCode::OK, Json(serde_json::json!({ "url": url }))).into_response(),
Err(e) => {
tracing::error!("B2 upload failed: {:?}", e);
(StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "File upload failed" }))).into_response()
(StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "storage_upload_failed", "detail": format!("{:?}", e) }))).into_response()
}
}
}

View file

@ -3,7 +3,7 @@
mod handlers;
mod admin;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@ -42,6 +42,7 @@ async fn main() {
.nest("/api/customers", handlers::router())
.nest("/api/admin/leads", admin::router())
.route("/health", get(|| async { "Customers OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -5,7 +5,7 @@
mod handlers;
mod admin;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@ -42,6 +42,7 @@ async fn main() {
.nest("/api/developers", handlers::router())
.nest("/api/admin/developers", admin::router())
.route("/health", get(|| async { "Developers OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -4,7 +4,7 @@
mod handlers;
mod admin;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use std::net::SocketAddr;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use contracts::ProfessionState;
@ -39,6 +39,7 @@ async fn main() {
.nest("/api/fitness-trainers", handlers::router())
.nest("/api/admin/fitness-trainers", admin::router())
.route("/health", get(|| async { "Fitness Trainers OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -5,7 +5,7 @@
mod handlers;
mod admin;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@ -42,6 +42,7 @@ async fn main() {
.nest("/api/graphic-designers", handlers::router())
.nest("/api/admin/graphic-designers", admin::router())
.route("/health", get(|| async { "Graphic Designers OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -776,8 +776,8 @@ async fn upload_document(
{
Ok(url) => url,
Err(e) => {
tracing::error!("B2 upload failed: {}", e);
return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "File upload failed" }))).into_response();
tracing::error!("B2 upload failed: {:?}", e);
return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "storage_upload_failed", "detail": format!("{:?}", e) }))).into_response();
}
};

View file

@ -3,7 +3,7 @@
mod handlers;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use cache::RedisPool;
use std::net::SocketAddr;
use std::sync::Arc;
@ -47,6 +47,7 @@ async fn main() {
let app = Router::new()
.nest("/api/jobseeker", handlers::router())
.route("/health", get(|| async { "Job Seekers OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -4,7 +4,7 @@
mod handlers;
mod admin;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@ -41,6 +41,7 @@ async fn main() {
.nest("/api/makeup-artists", handlers::router())
.nest("/api/admin/makeup-artists", admin::router())
.route("/health", get(|| async { "Makeup Artists OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -4,7 +4,7 @@
mod handlers;
mod admin;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@ -41,6 +41,7 @@ async fn main() {
.nest("/api/photographers", handlers::router())
.nest("/api/admin/photographers", admin::router())
.route("/health", get(|| async { "Photographers OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -6,7 +6,7 @@
mod handlers;
mod admin;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@ -43,6 +43,7 @@ async fn main() {
.nest("/api/social-media-managers", handlers::router())
.nest("/api/admin/social-media-managers", admin::router())
.route("/health", get(|| async { "Social Media Managers OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -5,7 +5,7 @@
mod handlers;
mod admin;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@ -42,6 +42,7 @@ async fn main() {
.nest("/api/tutors", handlers::router())
.nest("/api/admin/tutors", admin::router())
.route("/health", get(|| async { "Tutors OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -4,7 +4,7 @@
mod handlers;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@ -40,6 +40,7 @@ async fn main() {
let app = Router::new()
.nest("/api/ugc-content-creators", handlers::router())
.route("/health", get(|| async { "UGC Content Creators OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -4,7 +4,7 @@
mod handlers;
mod admin;
use axum::{routing::get, Router};
use axum::{extract::DefaultBodyLimit, routing::get, Router};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@ -41,6 +41,7 @@ async fn main() {
.nest("/api/video-editors", handlers::router())
.nest("/api/admin/video-editors", admin::router())
.route("/health", get(|| async { "Video Editors OK" }))
.layer(DefaultBodyLimit::max(11 * 1024 * 1024))
.with_state(state);
let port: u16 = std::env::var("PORT")

View file

@ -898,7 +898,7 @@ async fn upload_document(
// the outermost ".context()" message — that's what actually names
// the root cause (auth, DNS, timeout, etc.).
tracing::error!("B2 upload failed: {:?}", e);
return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "File upload failed" }))).into_response();
return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "storage_upload_failed", "detail": format!("{:?}", e) }))).into_response();
}
};