Compare commits
8 Commits
v1.7.2
...
server/14-
| Author | SHA1 | Date | |
|---|---|---|---|
| a821aeb129 | |||
| 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.
|
||||
|
||||
## [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.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",
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.5",
|
||||
"private": true,
|
||||
"prisma": {
|
||||
"seed": "bun prisma/seed.ts"
|
||||
|
||||
@@ -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'll get back to you as soon as possible.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { prisma } from "@/lib";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET(
|
||||
|
||||
@@ -2,7 +2,10 @@ import _ from "lodash";
|
||||
import { NextResponse } from "next/server";
|
||||
import prisma from "@/lib/prisma";
|
||||
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";
|
||||
|
||||
export { POST, GET };
|
||||
@@ -72,15 +75,9 @@ async function GET(request: Request) {
|
||||
const search = searchParams.get("search");
|
||||
const category = searchParams.get("category");
|
||||
const page = searchParams.get("page");
|
||||
const takeData = 5;
|
||||
const takeData = 10;
|
||||
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 {
|
||||
if (category === "beranda") {
|
||||
const blockUserId = await prisma.blockedUser
|
||||
|
||||
@@ -45,10 +45,16 @@ async function gracefulShutdown(): Promise<void> {
|
||||
console.log("[Prisma] Semua koneksi ditutup");
|
||||
}
|
||||
|
||||
// Register shutdown handlers (hanya di environment Node.js)
|
||||
if (typeof process !== "undefined") {
|
||||
process.on("SIGINT", gracefulShutdown);
|
||||
process.on("SIGTERM", gracefulShutdown);
|
||||
// Register shutdown handlers (hanya di environment Node.js server)
|
||||
// Cegah duplikasi listener dengan cek listenerCount terlebih dahulu
|
||||
// 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);
|
||||
}
|
||||
if (process.listenerCount("SIGTERM") === 0) {
|
||||
process.on("SIGTERM", gracefulShutdown);
|
||||
}
|
||||
}
|
||||
|
||||
export default prisma;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { prisma } from "@/lib";
|
||||
import { describe, test, expect } from "bun:test";
|
||||
|
||||
describe("coba test", () => {
|
||||
test("coba", async () => {
|
||||
const user = await prisma.user.findMany();
|
||||
expect(user).not.toBeEmpty();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user