From 38239c52d6d083d0c6ee6fde94f09e16fa7201dd Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Tue, 14 Apr 2026 15:17:45 +0800 Subject: [PATCH] fix: override MIME type for PDF uploads & fix build errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix PDF upload failing on Android due to wrong MIME type (image/pdf โ†’ application/pdf) - Add safe JSON parsing for external storage responses with incorrect Content-Type headers - Add detailed upload logging for debugging (file name, type, size, response status) - Fix TypeScript error: missing prisma import in forum preview-report-posting route - Fix ESLint warning: unescaped apostrophe in support-center page - Add DEBUG_UPLOAD_FILE.md documentation for troubleshooting guide Fixes upload issue where Android devices couldn't upload PDF files due to MIME type mismatch Co-authored-by: Qwen-Coder --- DEBUG_UPLOAD_FILE.md | 170 ++++++++++++++++++ src/app/(support)/support-center/page.tsx | 2 +- src/app/api/mobile/file/route.ts | 139 ++++++++++++-- .../[id]/preview-report-posting/route.ts | 1 + 4 files changed, 297 insertions(+), 15 deletions(-) create mode 100644 DEBUG_UPLOAD_FILE.md diff --git a/DEBUG_UPLOAD_FILE.md b/DEBUG_UPLOAD_FILE.md new file mode 100644 index 00000000..5fda9054 --- /dev/null +++ b/DEBUG_UPLOAD_FILE.md @@ -0,0 +1,170 @@ +# Debug Guide: Upload File Android vs iOS + +## ๐Ÿ“ฑ Problem +- โœ… Upload **IMAGE** berhasil di iOS dan Android +- โŒ Upload **PDF** gagal di Android dengan error: `Status: 400 Bad Request` + +## ๐Ÿ” Root Cause (DITEMUKAN!) + +### **Masalah MIME Type PDF** +Dari log upload: +``` +File details: + - Name: 154ce3b0-6fc0-4a39-9e09-3f9aa2b19300.pdf + - Type: image/pdf โ† โŒ SALAH! + - Size: 26534 bytes +``` + +**Yang benar:** +- โŒ `image/pdf` (salah - tidak ada MIME type ini) +- โœ… `application/pdf` (benar - standard MIME type untuk PDF) + +### **Kenapa Terjadi?** +Mobile app (Android) salah set MIME type saat mengirim file PDF. Kemungkinan: +1. File picker/set MIME type salah di mobile code +2. Android WebView auto-detect MIME type incorrectly +3. Mobile app hardcoded MIME type yang salah + +## ๐Ÿ› ๏ธ Solusi yang Sudah Diterapkan + +### File: `src/app/api/mobile/file/route.ts` + +**Fix #1: Safe JSON Parsing** +- โœ… Cek response sebagai text dulu, lalu parse JSON +- โœ… Handle Content-Type yang salah dari external storage + +**Fix #2: MIME Type Override (LATEST)** +- โœ… Deteksi file PDF dari extension (.pdf) +- โœ… Override MIME type ke `application/pdf` jika salah +- โœ… Rebuild FormData dengan file yang sudah difix + +**Code:** +```typescript +// Jika file PDF tapi type bukan application/pdf, fix it +if (fileName.endsWith(".pdf") && originalType !== "application/pdf") { + console.log("โš ๏ธ WARNING: PDF file has wrong MIME type:", originalType); + console.log("๐Ÿ”ง Overriding to: application/pdf"); + + // Create new File with correct MIME type + const buffer = await file.arrayBuffer(); + fixedFile = new File([buffer], file.name, { + type: "application/pdf", + lastModified: file.lastModified, + }); + + // Rebuild formData with fixed file + formData.set("file", fixedFile); +} +``` + +## ๐Ÿงช Cara Testing + +### 1. **Test Upload dari Android** +Coba upload file PDF dari Android dan perhatikan log di terminal: + +```bash +# Log yang akan muncul: +=== UPLOAD REQUEST START === +dirId: xxx +File details: + - Name: dokumen.pdf + - Type: application/pdf + - Size: 1234567 bytes + - Size (KB): 1205.63 +=========================== +Directory key: xxx +=== EXTERNAL STORAGE RESPONSE === +Status: 400 +Status Text: Bad Request +Content-Type: text/html; charset=utf-8 +================================= +=== ERROR: Non-JSON Response === +Response text: Unsupported file format... +================================= +``` + +### 2. **Informasi yang Perlu Dicari:** +Dari log di atas, perhatikan: +- **File size** โ†’ Berapa MB? (mungkin terlalu besar?) +- **File type** โ†’ `application/pdf` atau yang lain? +- **External storage response** โ†’ Status code & message? +- **Error text** โ†’ Apa yang dikembalikan server external? + +### 3. **Compare iOS vs Android** +Upload file yang sama dari iOS dan Android, bandingkan log-nya. + +## ๐Ÿ“Š Expected Log Output + +### โœ… Success Case: +``` +=== UPLOAD REQUEST START === +dirId: investment +File details: + - Name: proposal.pdf + - Type: application/pdf + - Size: 524288 bytes + - Size (KB): 512.00 +=========================== +Directory key: investment +=== EXTERNAL STORAGE RESPONSE === +Status: 200 +Status Text: OK +Content-Type: application/json +================================= +โœ… Upload SUCCESS +``` + +### โŒ Failed Case (Non-JSON Response): +``` +=== UPLOAD REQUEST START === +dirId: investment +File details: + - Name: proposal.pdf + - Type: application/pdf + - Size: 5242880 bytes โ† Mungkin terlalu besar? + - Size (KB): 5120.00 +=========================== +=== EXTERNAL STORAGE RESPONSE === +Status: 413 โ† Payload Too Large? +Content-Type: text/html +================================= +=== ERROR: Non-JSON Response === +Response text: 413 Request Entity Too Large +================================= +``` + +## ๐Ÿ”ง Next Steps (Setelah Testing) + +Berdasarkan log, kita bisa identify masalahnya: + +### **Jika masalah FILE SIZE:** +- Tambahkan limit validation di frontend +- Compress PDF sebelum upload +- Increase external storage limit + +### **Jika masalah FILE FORMAT:** +- Validate file type sebelum upload +- Convert format jika perlu +- Update external storage allowed formats + +### **Jika masalah NETWORK/HEADERS:** +- Check user-agent differences +- Validate Authorization header +- Check CORS settings + +## ๐Ÿ“ Checklist Testing +- [ ] Test upload PDF kecil (< 1MB) dari Android +- [ ] Test upload PDF besar (> 5MB) dari Android +- [ ] Test upload PDF dari iOS (baseline) +- [ ] Compare log output iOS vs Android +- [ ] Check file type yang dikirim +- [ ] Check file size yang dikirim +- [ ] Check response dari external storage + +## ๐ŸŽฏ Goal +Dari log yang detail, kita bisa tahu **exact reason** kenapa Android fail, lalu fix dengan tepat. + +--- + +**Last Updated:** 2026-04-14 +**Status:** โœ… Logging added, ready for testing diff --git a/src/app/(support)/support-center/page.tsx b/src/app/(support)/support-center/page.tsx index 9b23a078..9aefdc16 100644 --- a/src/app/(support)/support-center/page.tsx +++ b/src/app/(support)/support-center/page.tsx @@ -98,7 +98,7 @@ export default function SupportCenter() { Support Center - Send us a message and we'll get back to you as soon as possible. + Send us a message and we'll get back to you as soon as possible. diff --git a/src/app/api/mobile/file/route.ts b/src/app/api/mobile/file/route.ts index b3809d93..d8092876 100644 --- a/src/app/api/mobile/file/route.ts +++ b/src/app/api/mobile/file/route.ts @@ -2,14 +2,50 @@ import { funGetDirectoryNameByValue } from "@/app_modules/_global/fun/get"; import { NextResponse } from "next/server"; export async function POST(request: Request) { - const formData = await request.formData(); - const dirId = formData.get("dirId"); - - const keyOfDirectory = await funGetDirectoryNameByValue({ - value: dirId as string, - }); - try { + const formData = await request.formData(); + const dirId = formData.get("dirId"); + const file = formData.get("file") as File | null; + + // === LOGGING: Request Details === + console.log("=== UPLOAD REQUEST START ==="); + console.log("dirId:", dirId); + console.log("File details:"); + console.log(" - Name:", file?.name); + console.log(" - Type:", file?.type); + console.log(" - Size:", file?.size, "bytes"); + console.log(" - Size (KB):", file ? (file.size / 1024).toFixed(2) : "N/A"); + console.log("==========================="); + + // FIX: Override MIME type jika salah (mobile app kadang kirim image/pdf) + let fixedFile = file; + if (file) { + const fileName = file.name.toLowerCase(); + const originalType = file.type.toLowerCase(); + + // Jika file PDF tapi type bukan application/pdf, fix it + if (fileName.endsWith(".pdf") && originalType !== "application/pdf") { + console.log("โš ๏ธ WARNING: PDF file has wrong MIME type:", originalType); + console.log("๐Ÿ”ง Overriding to: application/pdf"); + + // Create new File with correct MIME type + const buffer = await file.arrayBuffer(); + fixedFile = new File([buffer], file.name, { + type: "application/pdf", + lastModified: file.lastModified, + }); + + // Rebuild formData with fixed file + formData.set("file", fixedFile); + } + } + + const keyOfDirectory = await funGetDirectoryNameByValue({ + value: dirId as string, + }); + + console.log("Directory key:", keyOfDirectory); + const res = await fetch("https://wibu-storage.wibudev.com/api/upload", { method: "POST", body: formData, @@ -18,9 +54,70 @@ export async function POST(request: Request) { }, }); - const dataRes = await res.json(); + // === LOGGING: Response Details === + console.log("=== EXTERNAL STORAGE RESPONSE ==="); + console.log("Status:", res.status); + console.log("Status Text:", res.statusText); + console.log("Content-Type:", res.headers.get("content-type")); + console.log("================================="); + + // Cek content-type sebelum parse JSON + const contentType = res.headers.get("content-type") || ""; + let dataRes; + + // Try parse JSON untuk semua response (beberapa server salah set content-type) + try { + const rawResponse = await res.text(); + + // Coba parse sebagai JSON + try { + dataRes = JSON.parse(rawResponse); + console.log("โœ… Successfully parsed response as JSON"); + } catch { + // Bukan JSON - gunakan raw text + console.log("โš ๏ธ Response is not JSON, using raw text"); + + if (res.ok) { + // Success tapi bukan JSON - return success response + return NextResponse.json( + { + success: true, + message: "Success upload file " + keyOfDirectory, + }, + { status: 200 } + ); + } else { + return NextResponse.json( + { + success: false, + message: "Upload failed", + error: rawResponse.substring(0, 500), + fileDetails: { + name: file?.name, + type: file?.type, + size: file?.size, + } + }, + { status: res.status || 400 } + ); + } + } + } catch (readError) { + console.log("โŒ Failed to read response body"); + console.log("Read error:", (readError as Error).message); + + return NextResponse.json( + { + success: false, + message: "Failed to read response", + reason: (readError as Error).message, + }, + { status: 500 } + ); + } if (res.ok) { + console.log("โœ… Upload SUCCESS"); return NextResponse.json( { success: true, @@ -30,20 +127,34 @@ export async function POST(request: Request) { { status: 200 } ); } else { - const errorText = await res.text(); - console.log(`Failed upload ${keyOfDirectory}: ${errorText}`); + console.log("โŒ Upload FAILED"); + console.log("Response:", dataRes); + + const errorMessage = dataRes.message || dataRes.error || JSON.stringify(dataRes); return NextResponse.json( - { success: false, message: errorText }, - { status: 400 } + { + success: false, + message: errorMessage || "Upload failed", + fileDetails: { + name: file?.name, + type: file?.type, + size: file?.size, + } + }, + { status: res.status || 400 } ); } } catch (error) { - console.log("Error upload >>", (error as Error).message || error); + console.log("=== CATCH ERROR ==="); + console.log("Error:", (error as Error).message); + console.log("Stack:", (error as Error).stack); + console.log("==================="); + return NextResponse.json( { success: false, message: "Failed upload file", - reason: (error as Error).message || error, + reason: (error as Error).message || "Unknown error", }, { status: 500 } ); diff --git a/src/app/api/mobile/forum/[id]/preview-report-posting/route.ts b/src/app/api/mobile/forum/[id]/preview-report-posting/route.ts index b68db0eb..ea671197 100644 --- a/src/app/api/mobile/forum/[id]/preview-report-posting/route.ts +++ b/src/app/api/mobile/forum/[id]/preview-report-posting/route.ts @@ -1,3 +1,4 @@ +import { prisma } from "@/lib"; import { NextResponse } from "next/server"; export async function GET(