Compare commits

..

3 Commits

Author SHA1 Message Date
0dd939b979 chore(release): 1.7.5 2026-04-14 15:34:52 +08:00
38239c52d6 fix: override MIME type for PDF uploads & fix build errors
- 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 <qwen-coder@alibabacloud.com>
2026-04-14 15:17:45 +08:00
a71997b4ef Fix version 1.7.4
## no issue
2026-03-30 15:49:07 +08:00
7 changed files with 308 additions and 18 deletions

View File

@@ -2,6 +2,13 @@
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
## [1.7.5](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.7.4...v1.7.5) (2026-04-14)
### Bug Fixes
* override MIME type for PDF uploads & fix build errors ([38239c5](https://wibugit.wibudev.com/wibu/hipmi/commit/38239c52d6d083d0c6ee6fde94f09e16fa7201dd))
## [1.7.4](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.7.3...v1.7.4) (2026-03-30)
## [1.7.3](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.7.2...v1.7.3) (2026-03-27)

170
DEBUG_UPLOAD_FILE.md Normal file
View File

@@ -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: <html><body>413 Request Entity Too Large</body></html>
=================================
```
## 🔧 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

View File

@@ -1,6 +1,6 @@
{
"name": "hipmi",
"version": "1.7.4",
"version": "1.7.5",
"private": true,
"prisma": {
"seed": "bun prisma/seed.ts"

View File

@@ -98,7 +98,7 @@ export default function SupportCenter() {
<Title>Support Center</Title>
</Group>
<Text align="center">
Send us a message and we'll get back to you as soon as possible.
Send us a message and we&apos;ll get back to you as soon as possible.
</Text>
</Stack>
</Stack>

View File

@@ -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 }
);

View File

@@ -1,3 +1,4 @@
import { prisma } from "@/lib";
import { NextResponse } from "next/server";
export async function GET(

View File

@@ -45,9 +45,10 @@ async function gracefulShutdown(): Promise<void> {
console.log("[Prisma] Semua koneksi ditutup");
}
// Register shutdown handlers (hanya di environment Node.js)
// Register shutdown handlers (hanya di environment Node.js server)
// Cegah duplikasi listener dengan cek listenerCount terlebih dahulu
if (typeof process !== "undefined") {
// IMPORTANT: Bungkus dalam check untuk mencegah error di browser
if (typeof process !== "undefined" && typeof process.listenerCount === "function") {
if (process.listenerCount("SIGINT") === 0) {
process.on("SIGINT", gracefulShutdown);
}