From aa2132f02c3e69ab618074dc51780295e1197558 Mon Sep 17 00:00:00 2001 From: Kavehhn174 Date: Sat, 15 Aug 2026 03:00:42 +0330 Subject: [PATCH] fix: disable AWS SDK checksum defaults for SeaweedFS uploads PutObject was failing with XML parse errors because flexible checksum headers are unsupported; match SeaweedFS recommended S3 client settings. --- utils/s3Client.js | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/utils/s3Client.js b/utils/s3Client.js index 803caf8..496d89d 100644 --- a/utils/s3Client.js +++ b/utils/s3Client.js @@ -43,19 +43,45 @@ const buildPublicUrl = (bucketName, fileKey) => { const getS3Client = () => { if (!s3ClientInstance) { + const endpoint = String(config.S3_ENDPOINT || '').replace(/\/$/, ''); s3ClientInstance = new S3Client({ - endpoint: config.S3_ENDPOINT, - region: config.S3_REGION, + endpoint, + region: config.S3_REGION || 'us-east-1', credentials: { accessKeyId: config.S3_ACCESS_KEY, secretAccessKey: config.S3_SECRET_KEY }, - forcePathStyle: config.S3_FORCE_PATH_STYLE + forcePathStyle: config.S3_FORCE_PATH_STYLE !== false, + // SeaweedFS / MinIO: AWS SDK v3 default flexible checksums break PutObject + // (XML parse / unexpected content). Only checksum when the API requires it. + // See: https://github.com/seaweedfs/seaweedfs/wiki/nodejs-with-Seaweed-S3 + requestChecksumCalculation: 'WHEN_REQUIRED', + responseChecksumValidation: 'WHEN_REQUIRED', + useDualstackEndpoint: false }); + logger.info(`[S3 Storage] Client ready → ${endpoint} (pathStyle=${config.S3_FORCE_PATH_STYLE !== false})`); } return s3ClientInstance; }; +const logS3Error = (label, error) => { + const status = error?.$metadata?.httpStatusCode; + const raw = error?.$response; + let bodyPreview = ''; + try { + const body = raw?.body || raw?.reason || ''; + if (typeof body === 'string') bodyPreview = body.slice(0, 300); + else if (body && typeof body.toString === 'function') bodyPreview = String(body).slice(0, 300); + } catch { + bodyPreview = ''; + } + logger.error( + `[S3 Storage ERROR] ${label}: ${error.message}` + + (status ? ` (HTTP ${status})` : '') + + (bodyPreview ? ` body=${bodyPreview.replace(/\s+/g, ' ')}` : '') + ); +}; + const uploadToTempBucket = async (fileBuffer, filename, contentType = 'application/octet-stream') => { try { const client = getS3Client(); @@ -70,7 +96,8 @@ const uploadToTempBucket = async (fileBuffer, filename, contentType = 'applicati logger.info(`[S3 Storage] Uploaded temp file: ${filename} to bucket ${config.S3_TEMP_BUCKET}`); return { tempFileName: filename, bucket: config.S3_TEMP_BUCKET }; } catch (error) { - logger.error(`[S3 Storage ERROR] Temp upload failed for ${filename}: ${error.message}`); + logS3Error(`Temp upload failed for ${filename}`, error); + // Mock fallback for test environment when S3 is unavailable if (config.NODE_ENV === 'test' || error.code === 'ECONNREFUSED') { logger.warn(`[S3 Storage MOCK] Simulated temp upload for ${filename}`); return { tempFileName: filename, bucket: config.S3_TEMP_BUCKET }; @@ -109,7 +136,7 @@ const commitTempFile = async (tempFilename, destinationKey = null, targetBucketK logger.info(`[S3 Storage] Committed ${tempFilename} → ${targetBucket}/${targetKey}`); return { fileKey: targetKey, bucket: targetBucket, fileUrl }; } catch (error) { - logger.error(`[S3 Storage ERROR] Failed to commit temp file ${tempFilename}: ${error.message}`); + logS3Error(`Failed to commit temp file ${tempFilename}`, error); if (config.NODE_ENV === 'test' || error.code === 'ECONNREFUSED') { logger.warn(`[S3 Storage MOCK] Simulated file commit for ${targetKey}`); return {