Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0dd939b979 | |||
| 38239c52d6 | |||
| a71997b4ef | |||
| 0f584f8c72 | |||
| 445801941d | |||
| defafe694f | |||
| cd3a9cc223 |
11
CHANGELOG.md
11
CHANGELOG.md
@@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
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.
|
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)
|
||||||
|
|
||||||
## [1.7.2](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.7.1...v1.7.2) (2026-03-13)
|
## [1.7.2](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.7.1...v1.7.2) (2026-03-13)
|
||||||
|
|
||||||
## [1.7.1](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.7.0...v1.7.1) (2026-03-11)
|
## [1.7.1](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.7.0...v1.7.1) (2026-03-11)
|
||||||
|
|||||||
170
DEBUG_UPLOAD_FILE.md
Normal file
170
DEBUG_UPLOAD_FILE.md
Normal 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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hipmi",
|
"name": "hipmi",
|
||||||
"version": "1.7.2",
|
"version": "1.7.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "bun prisma/seed.ts"
|
"seed": "bun prisma/seed.ts"
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export default function SupportCenter() {
|
|||||||
<Title>Support Center</Title>
|
<Title>Support Center</Title>
|
||||||
</Group>
|
</Group>
|
||||||
<Text align="center">
|
<Text align="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.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -2,14 +2,50 @@ import { funGetDirectoryNameByValue } from "@/app_modules/_global/fun/get";
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
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 {
|
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", {
|
const res = await fetch("https://wibu-storage.wibudev.com/api/upload", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
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) {
|
if (res.ok) {
|
||||||
|
console.log("✅ Upload SUCCESS");
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
success: true,
|
success: true,
|
||||||
@@ -30,20 +127,34 @@ export async function POST(request: Request) {
|
|||||||
{ status: 200 }
|
{ status: 200 }
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const errorText = await res.text();
|
console.log("❌ Upload FAILED");
|
||||||
console.log(`Failed upload ${keyOfDirectory}: ${errorText}`);
|
console.log("Response:", dataRes);
|
||||||
|
|
||||||
|
const errorMessage = dataRes.message || dataRes.error || JSON.stringify(dataRes);
|
||||||
return NextResponse.json(
|
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) {
|
} 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(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
message: "Failed upload file",
|
message: "Failed upload file",
|
||||||
reason: (error as Error).message || error,
|
reason: (error as Error).message || "Unknown error",
|
||||||
},
|
},
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { prisma } from "@/lib";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import _ from "lodash";
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
|
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
|
||||||
import { NotificationMobileBodyType, NotificationMobileTitleType } from "../../../../../types/type-mobile-notification";
|
import {
|
||||||
|
NotificationMobileBodyType,
|
||||||
|
NotificationMobileTitleType,
|
||||||
|
} from "../../../../../types/type-mobile-notification";
|
||||||
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
|
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
|
||||||
|
|
||||||
export { POST, GET };
|
export { POST, GET };
|
||||||
@@ -72,15 +75,9 @@ async function GET(request: Request) {
|
|||||||
const search = searchParams.get("search");
|
const search = searchParams.get("search");
|
||||||
const category = searchParams.get("category");
|
const category = searchParams.get("category");
|
||||||
const page = searchParams.get("page");
|
const page = searchParams.get("page");
|
||||||
const takeData = 5;
|
const takeData = 10;
|
||||||
const skipData = (Number(page) - 1) * takeData;
|
const skipData = (Number(page) - 1) * takeData;
|
||||||
|
|
||||||
// console.log("authorId", authorId);
|
|
||||||
// console.log("userLoginId", userLoginId);
|
|
||||||
// console.log("search", search);
|
|
||||||
// console.log("category", category);
|
|
||||||
console.log("page", page);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (category === "beranda") {
|
if (category === "beranda") {
|
||||||
const blockUserId = await prisma.blockedUser
|
const blockUserId = await prisma.blockedUser
|
||||||
|
|||||||
@@ -45,10 +45,16 @@ async function gracefulShutdown(): Promise<void> {
|
|||||||
console.log("[Prisma] Semua koneksi ditutup");
|
console.log("[Prisma] Semua koneksi ditutup");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register shutdown handlers (hanya di environment Node.js)
|
// Register shutdown handlers (hanya di environment Node.js server)
|
||||||
if (typeof process !== "undefined") {
|
// Cegah duplikasi listener dengan cek listenerCount terlebih dahulu
|
||||||
process.on("SIGINT", gracefulShutdown);
|
// IMPORTANT: Bungkus dalam check untuk mencegah error di browser
|
||||||
process.on("SIGTERM", gracefulShutdown);
|
if (typeof process !== "undefined" && typeof process.listenerCount === "function") {
|
||||||
|
if (process.listenerCount("SIGINT") === 0) {
|
||||||
|
process.on("SIGINT", gracefulShutdown);
|
||||||
|
}
|
||||||
|
if (process.listenerCount("SIGTERM") === 0) {
|
||||||
|
process.on("SIGTERM", gracefulShutdown);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default prisma;
|
export default prisma;
|
||||||
|
|||||||
Reference in New Issue
Block a user