feat: Implementasi pagination pada endpoint mobile donation

4
      5 - Menambahkan pagination pada endpoint GET /api/mobile/donation
      6 - Menambahkan pagination pada endpoint GET /api/mobile/donation/[id]/news
      7 - Menambahkan pagination pada endpoint GET /api/mobile/donation/[id]/donatur
      8 - Memperbaiki validasi payload pada endpoint POST /api/mobile/auth/device-tokens
      9 - Menangani struktur payload yang bersarang dan langsung pada device token endpoint
     10 - Menambahkan informasi pagination ke dalam respons API

### NO Issue
This commit is contained in:
2026-02-10 17:31:39 +08:00
parent 934d6a3ef1
commit e89886e1db
8 changed files with 158 additions and 19 deletions

View File

@@ -1,5 +1,5 @@
File utama: src/app/api/mobile/donation/[id]/[status]/route.ts File utama: src/app/api/mobile/donation/[id]/donatur/route.ts
Terapkan pagination pada file "File utama" pada method GET Terapkan pagination pada file "File utama" pada method GET
Analisa juga file "File utama", jika belum memiliki page dari seachParams maka terapkan. Juga pastikan take dan skip sudah sesuai dengan pagination. Buat default nya menjadi 10 untuk take data Analisa juga file "File utama", jika belum memiliki page dari seachParams maka terapkan. Juga pastikan take dan skip sudah sesuai dengan pagination. Buat default nya menjadi 10 untuk take data
@@ -33,4 +33,13 @@ Buatkan auto input untuk method POST dengan data yang dibutuhkan sesuai dengan s
- rekening: string - rekening: string
- imageId: number ( cm60j9q3m000xc9dc584v8rh8 ) - imageId: number ( cm60j9q3m000xc9dc584v8rh8 )
Untuk sisa nya anda bisa bebas mengisi data tersebut. Untuk sisa nya anda bisa bebas mengisi data tersebut.
<!-- COMMIT & PUSH -->
Branch: mobile-api/10-feb-26
Jalankan perintah ini: git checkout -b "Branch"
Setelah itu jalankan perintah ini: git add .
Setelah itu jalankan perintah ini: git commit -m "
<Berikan semua catatan perubahan pada branch ini, tampilan pada saya dan pastikan dalam bahasa indonesia. Saya akan cek baru saya akan berikan perintah push>
"
Setelah itu jalankan perintah ini: git push origin "Branch"

View File

@@ -2,7 +2,7 @@
## Project Overview ## Project Overview
HIPMI (Himpunan Pengusaha Muda Indonesia) is a comprehensive Next.js-based web application built for the Indonesian Young Entrepreneurs Association. The project is a sophisticated platform that provides multiple business functionalities including investment management, donations, events, job listings, forums, voting systems, and collaborative projects. HIPMI (Himpunan Pengusaha Muya Indonesia) is a comprehensive Next.js-based web application built for the Indonesian Young Entrepreneurs Association. The project is a sophisticated platform that provides multiple business functionalities including investment management, donations, events, job listings, forums, voting systems, and collaborative projects.
### Key Technologies ### Key Technologies
- **Framework**: Next.js 13+ (with App Router) - **Framework**: Next.js 13+ (with App Router)

View File

@@ -4,13 +4,42 @@ import { prisma } from "@/lib";
export { POST, GET }; export { POST, GET };
async function POST(request: NextRequest) { async function POST(request: NextRequest) {
const { data } = await request.json();
try { try {
// Parse the request body - can accept either nested under 'data' or directly
const requestBody = await request.json();
// Check if the data is nested under 'data' property (as described in the issue)
// or if it's directly in the request body (more common pattern)
const payload = requestBody.data ? requestBody.data : requestBody;
const { userId, platform, deviceId, model, appVersion, fcmToken } = payload;
const { userId, platform, deviceId, model, appVersion, fcmToken } = data; // Validate required fields
if (!fcmToken) { if (!fcmToken) {
return NextResponse.json({ error: "Missing Token" }, { status: 400 }); return NextResponse.json(
{ error: "Missing FCM token", field: "fcmToken" },
{ status: 400 }
);
}
if (!userId) {
return NextResponse.json(
{ error: "Missing user ID", field: "userId" },
{ status: 400 }
);
}
// Verify that the user exists before creating/updating the device token
const userExists = await prisma.user.findUnique({
where: { id: userId },
select: { id: true }
});
if (!userExists) {
return NextResponse.json(
{ error: "User not found", field: "userId" },
{ status: 404 }
);
} }
const existing = await prisma.tokenUserDevice.findFirst({ const existing = await prisma.tokenUserDevice.findFirst({
@@ -23,7 +52,6 @@ async function POST(request: NextRequest) {
}, },
}); });
console.log("✅ EX", existing); console.log("✅ EX", existing);
let deviceToken; let deviceToken;
@@ -31,7 +59,7 @@ async function POST(request: NextRequest) {
if (existing) { if (existing) {
deviceToken = await prisma.tokenUserDevice.update({ deviceToken = await prisma.tokenUserDevice.update({
where: { where: {
id: existing?.id, id: existing.id,
}, },
data: { data: {
platform, platform,
@@ -43,7 +71,7 @@ async function POST(request: NextRequest) {
}, },
}); });
} else { } else {
// Buat baru jika belum ada // Create new device token record
deviceToken = await prisma.tokenUserDevice.create({ deviceToken = await prisma.tokenUserDevice.create({
data: { data: {
token: fcmToken, token: fcmToken,
@@ -58,9 +86,16 @@ async function POST(request: NextRequest) {
} }
return NextResponse.json({ success: true, data: deviceToken }); return NextResponse.json({ success: true, data: deviceToken });
} catch (error) { } catch (error: any) {
console.error("Error registering device token:", error);
// Return more informative error response
return NextResponse.json( return NextResponse.json(
{ error: (error as Error).message }, {
error: "Internal server error",
message: error.message || "An unexpected error occurred",
field: "server"
},
{ status: 500 } { status: 500 }
); );
} }

View File

@@ -1,6 +1,7 @@
import _ from "lodash"; import _ from "lodash";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET, PUT }; export { GET, PUT };
@@ -12,7 +13,7 @@ async function GET(
const fixStatus = _.startCase(status); const fixStatus = _.startCase(status);
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page")) || 1; const page = Number(searchParams.get("page")) || 1;
const takeData = 5 const takeData = PAGINATION_DEFAULT_TAKE
const skipData = page * takeData - takeData; const skipData = page * takeData - takeData;
let fixData; let fixData;

View File

@@ -1,5 +1,6 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { prisma } from "@/lib"; import { prisma } from "@/lib";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET }; export { GET };
@@ -7,7 +8,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
const { id } = params; const { id } = params;
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page")); const page = Number(searchParams.get("page"));
const takeData = 5; const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData; const skipData = page * takeData - takeData;
try { try {

View File

@@ -1,3 +1,4 @@
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
@@ -9,11 +10,12 @@ export async function GET(
let fixData; let fixData;
const { id } = params; const { id } = params;
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page")); const page = Number(searchParams.get("page")) || 1; // Default page 1 jika tidak ada atau invalid
const takeData = 10; const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData; const skipData = page * takeData - takeData;
fixData = await prisma.donasi_Invoice.findMany({ // Query data dengan pagination
const data = await prisma.donasi_Invoice.findMany({
take: page ? takeData : undefined, take: page ? takeData : undefined,
skip: page ? skipData : undefined, skip: page ? skipData : undefined,
orderBy: { orderBy: {
@@ -59,10 +61,31 @@ export async function GET(
}, },
}); });
// Hitung total data untuk pagination
const totalCount = await prisma.donasi_Invoice.count({
where: {
donasiId: id,
DonasiMaster_StatusInvoice: {
name: "Berhasil",
},
},
});
// Hitung total halaman
const totalPages = Math.ceil(totalCount / takeData);
fixData = data;
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
message: "Data berhasil diambil", message: "Data berhasil diambil",
data: fixData, data: fixData,
pagination: {
currentPage: page,
totalPages: totalPages,
totalData: totalCount,
dataPerPage: takeData,
},
}); });
} catch (error) { } catch (error) {
return NextResponse.json({ return NextResponse.json({

View File

@@ -8,6 +8,7 @@ import {
} from "../../../../../../../types/type-mobile-notification"; } from "../../../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile"; import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
import { funFindDonaturList } from "@/lib/mobile/donation/find-donatur-list"; import { funFindDonaturList } from "@/lib/mobile/donation/find-donatur-list";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { POST, GET, PUT, DELETE }; export { POST, GET, PUT, DELETE };
@@ -94,11 +95,16 @@ async function GET(
const { id } = params; const { id } = params;
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const category = searchParams.get("category"); const category = searchParams.get("category");
const page = Number(searchParams.get("page")) || 1; // Default page 1 jika tidak ada
const takeData = PAGINATION_DEFAULT_TAKE; // Default 10 data per halaman
const skipData = page * takeData - takeData;
let fixData; let fixData;
let totalCount = 0; // Untuk menghitung total data
try { try {
if (category === "get-all") { if (category === "get-all") {
fixData = await prisma.donasi_Kabar.findMany({ const data = await prisma.donasi_Kabar.findMany({
orderBy: { orderBy: {
updatedAt: "desc", updatedAt: "desc",
}, },
@@ -106,6 +112,8 @@ async function GET(
donasiId: id, donasiId: id,
active: true, active: true,
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: { select: {
id: true, id: true,
title: true, title: true,
@@ -113,6 +121,17 @@ async function GET(
createdAt: true, createdAt: true,
}, },
}); });
// Hitung total data untuk pagination
totalCount = await prisma.donasi_Kabar.count({
where: {
donasiId: id,
active: true,
},
});
fixData = data;
} else if (category === "get-one") { } else if (category === "get-one") {
const data = await prisma.donasi_Kabar.findUnique({ const data = await prisma.donasi_Kabar.findUnique({
where: { where: {
@@ -135,11 +154,24 @@ async function GET(
}; };
} }
// Hitung total halaman jika kategori adalah get-all
let pagination = undefined;
if (category === "get-all") {
const totalPages = Math.ceil(totalCount / takeData);
pagination = {
currentPage: page,
totalPages: totalPages,
totalData: totalCount,
dataPerPage: takeData,
};
}
return NextResponse.json({ return NextResponse.json({
status: 200, status: 200,
success: true, success: true,
message: "Berhasil mengambil kabar", message: "Berhasil mengambil kabar",
data: fixData, data: fixData,
pagination: pagination,
}); });
} catch (error) { } catch (error) {
console.error("[ERROR GET NEWS]", error); console.error("[ERROR GET NEWS]", error);

View File

@@ -4,6 +4,7 @@ import _ from "lodash";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { NotificationMobileBodyType } from "../../../../../types/type-mobile-notification"; import { NotificationMobileBodyType } from "../../../../../types/type-mobile-notification";
import { routeAdminMobile } from "@/lib/mobile/route-page-mobile"; import { routeAdminMobile } from "@/lib/mobile/route-page-mobile";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { POST, GET }; export { POST, GET };
@@ -125,7 +126,12 @@ async function GET(request: Request) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const category = searchParams.get("category"); const category = searchParams.get("category");
const authorId = searchParams.get("authorId"); const authorId = searchParams.get("authorId");
const page = Number(searchParams.get("page")) || 1; // Default page 1 jika tidak ada
const takeData = PAGINATION_DEFAULT_TAKE; // Default 10 data per halaman
const skipData = page * takeData - takeData;
let fixData; let fixData;
let totalCount = 0; // Untuk menghitung total data
try { try {
if (category === "beranda") { if (category === "beranda") {
@@ -137,6 +143,8 @@ async function GET(request: Request) {
donasiMaster_StatusDonasiId: "1", donasiMaster_StatusDonasiId: "1",
active: true, active: true,
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: { select: {
id: true, id: true,
imageId: true, imageId: true,
@@ -152,6 +160,14 @@ async function GET(request: Request) {
}, },
}); });
// Hitung total data untuk pagination
totalCount = await prisma.donasi.count({
where: {
donasiMaster_StatusDonasiId: "1",
active: true,
},
});
fixData = data.map((v: any) => ({ fixData = data.map((v: any) => ({
..._.omit(v, ["DonasiMaster_Durasi"]), ..._.omit(v, ["DonasiMaster_Durasi"]),
durasiDonasi: v.DonasiMaster_Durasi.name, durasiDonasi: v.DonasiMaster_Durasi.name,
@@ -164,6 +180,8 @@ async function GET(request: Request) {
where: { where: {
authorId: authorId, authorId: authorId,
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: { select: {
id: true, id: true,
nominal: true, nominal: true,
@@ -190,6 +208,13 @@ async function GET(request: Request) {
}, },
}); });
// Hitung total data untuk pagination
totalCount = await prisma.donasi_Invoice.count({
where: {
authorId: authorId,
},
});
fixData = data.map((v: any) => ({ fixData = data.map((v: any) => ({
..._.omit(v, ["DonasiMaster_StatusInvoice", "Donasi"]), ..._.omit(v, ["DonasiMaster_StatusInvoice", "Donasi"]),
statusInvoice: v.DonasiMaster_StatusInvoice.name, statusInvoice: v.DonasiMaster_StatusInvoice.name,
@@ -202,8 +227,21 @@ async function GET(request: Request) {
})); }));
} }
// Hitung total halaman
const totalPages = Math.ceil(totalCount / takeData);
return NextResponse.json( return NextResponse.json(
{ success: true, message: "Data berhasil diambil", data: fixData }, {
success: true,
message: "Data berhasil diambil",
data: fixData,
pagination: {
currentPage: page,
totalPages: totalPages,
totalData: totalCount,
dataPerPage: takeData,
}
},
{ status: 200 } { status: 200 }
); );
} catch (error) { } catch (error) {