Compare commits

...

5 Commits

Author SHA1 Message Date
1c227a2850 Fix Admin API Mobile
API – Admin Donation
- src/app/api/mobile/admin/donation/[id]/disbursement/route.ts
- src/app/api/mobile/admin/donation/[id]/donatur/route.ts
- src/app/api/mobile/admin/donation/route.ts

API – Master Data (Admin)
- src/app/api/mobile/admin/master/donation/route.ts
- src/app/api/mobile/admin/master/type-of-event/route.ts

API – Admin Voting
- src/app/api/mobile/admin/voting/route.ts

Docs
- PROMPT-AI.md
- QWEN.md

Deleted
- CHANGELOG_BRANCH.md

### No Issue
2026-02-18 17:22:54 +08:00
5bdb998d2e ### Fitur: Penambahan Pagination pada Endpoint Admin Mobile
#### Deskripsi Umum
Telah dilakukan penambahan fitur pagination pada beberapa endpoint admin mobile untuk meningkatkan kinerja dan pengalaman pengguna saat mengakses data dalam jumlah besar.

#### File yang Diubah

1. **src/app/api/mobile/admin/job/route.ts**
   - Ditambahkan parameter  dari
   - Diterapkan logika pagination dengan  (default 10) dan
   - Query  telah dimodifikasi untuk mendukung pagination

2. **src/app/api/mobile/admin/event/route.ts**
   - Diperbaiki definisi variabel  untuk memastikan tipe data yang konsisten
   - Ditambahkan default value 1 untuk parameter
   - Perhitungan  disesuaikan agar lebih efisien

3. **src/app/api/mobile/admin/event/[id]/participants/route.ts**
   - Ditambahkan parameter  dari
   - Diterapkan logika pagination dengan  (default 10) dan
   - Query  telah dimodifikasi untuk mendukung pagination

#### Tujuan Perubahan
- Meningkatkan kinerja aplikasi saat mengambil data dalam jumlah besar
- Memungkinkan pengguna untuk mengakses data secara bertahap melalui halaman-halaman
- Mengurangi beban server saat mengambil data dalam jumlah besar
- Memberikan pengalaman pengguna yang lebih baik saat mengakses data admin

#### Cara Penggunaan
Untuk menggunakan fitur pagination, cukup tambahkan parameter  pada query string saat melakukan permintaan ke endpoint yang telah dimodifikasi. Contoh:

Default jumlah data per halaman adalah 10 item.

### No Issue
2026-02-14 15:36:09 +08:00
b585aa3024 Fix Api Mobile
API – Admin Master Data
- src/app/api/mobile/admin/master/bank/route.ts
- src/app/api/mobile/admin/master/business-field/route.ts
- src/app/api/mobile/admin/master/business-field/[id]/route.ts

Docs
- PROMPT-AI.md

### No Issue
2026-02-13 17:40:25 +08:00
a8f9d2ac0d Fix API Mobile Admin
API – Admin User (Mobile)
- src/app/api/mobile/admin/user/route.ts

Docs
- PROMPT-AI.md

### No Issue
2026-02-12 17:42:06 +08:00
d43f3762a3 Fixed Bug Server
## Summary
This branch contains several bug fixes and performance improvements, primarily focusing on:
- Database connection management
- MQTT client stability
- Logging optimization
- API enhancements

## Detailed Changes

### Fixed Issues
1. **Database Connection Management**
   - Removed  from user-validate API route to prevent connection pool exhaustion
   - Added proper connection handling in global Prisma setup
   - Reduced logging verbosity in production environments

2. **MQTT Client Improvements**
   - Enhanced MQTT client initialization with proper error handling
   - Added reconnection logic with configurable intervals
   - Implemented cleanup functions to prevent memory leaks
   - Added separate initialization logic for server and client-side code

3. **Logging Optimization**
   - Removed excessive logging in middleware that was causing high CPU usage
   - Configured appropriate log levels for development and production

4. **Component Stability**
   - Added safety checks in text editor component to prevent MQTT operations on the server side
   - Improved MQTT publishing logic with client availability checks

### New Files
-  - Utility functions for safe database operations

### Modified Files
1.
   - Removed problematic  call

2.
   - Configured different logging levels for dev/prod
   - Removed process listeners that were causing disconnections
   - Exported prisma instance separately

3.
   - Removed excessive logging statements

4.
   - Enhanced initialization with error handling
   - Added reconnection and timeout configurations

5.
   - Added proper cleanup functions
   - Improved connection handling

6.
   - Added MQTT client availability checks
   - Prevented server-side MQTT operations

### Performance Improvements
- Reduced database connection overhead
- Optimized MQTT connection handling
- Eliminated unnecessary logging in production
- Better memory management with proper cleanup functions

### No Issue
2026-02-12 16:29:03 +08:00
23 changed files with 299 additions and 97 deletions

39
CHANGELOG_COMMIT.md Normal file
View File

@@ -0,0 +1,39 @@
## Catatan Perubahan untuk Commit
### Fitur: Penambahan Pagination pada Endpoint Admin Mobile
#### Deskripsi Umum
Telah dilakukan penambahan fitur pagination pada beberapa endpoint admin mobile untuk meningkatkan kinerja dan pengalaman pengguna saat mengakses data dalam jumlah besar.
#### File yang Diubah
1. **src/app/api/mobile/admin/job/route.ts**
- Ditambahkan parameter `page` dari `searchParams`
- Diterapkan logika pagination dengan `takeData` (default 10) dan `skipData`
- Query `prisma.job.findMany` telah dimodifikasi untuk mendukung pagination
2. **src/app/api/mobile/admin/event/route.ts**
- Diperbaiki definisi variabel `page` untuk memastikan tipe data yang konsisten
- Ditambahkan default value 1 untuk parameter `page`
- Perhitungan `skipData` disesuaikan agar lebih efisien
3. **src/app/api/mobile/admin/event/[id]/participants/route.ts**
- Ditambahkan parameter `page` dari `searchParams`
- Diterapkan logika pagination dengan `takeData` (default 10) dan `skipData`
- Query `prisma.event_Peserta.findMany` telah dimodifikasi untuk mendukung pagination
#### Tujuan Perubahan
- Meningkatkan kinerja aplikasi saat mengambil data dalam jumlah besar
- Memungkinkan pengguna untuk mengakses data secara bertahap melalui halaman-halaman
- Mengurangi beban server saat mengambil data dalam jumlah besar
- Memberikan pengalaman pengguna yang lebih baik saat mengakses data admin
#### Cara Penggunaan
Untuk menggunakan fitur pagination, cukup tambahkan parameter `page` pada query string saat melakukan permintaan ke endpoint yang telah dimodifikasi. Contoh:
```
GET /api/mobile/admin/job?page=2
GET /api/mobile/admin/event?page=3
GET /api/mobile/admin/event/{id}/participants?page=1
```
Default jumlah data per halaman adalah 10 item.

View File

@@ -1,5 +1,5 @@
File utama: src/app/api/mobile/donation/[id]/donatur/route.ts File utama: src/app/api/mobile/admin/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

View File

@@ -198,4 +198,4 @@ References: #issue-number
### Data Protection ### Data Protection
- Encrypted tokens - Encrypted tokens
- Secure API routes - Secure API routes
- Proper CORS configuration - Proper CORS configuration

View File

@@ -10,6 +10,7 @@ import {
NotificationMobileTitleType, NotificationMobileTitleType,
} 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 { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { POST, GET }; export { POST, GET };
@@ -154,7 +155,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const category = searchParams.get("category"); const category = searchParams.get("category");
const page = searchParams.get("page"); const page = searchParams.get("page");
const takeData = 10; const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = Number(page) * takeData - takeData; const skipData = Number(page) * takeData - takeData;
console.log("[CATEGORY]", category); console.log("[CATEGORY]", category);
@@ -174,6 +175,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
id: true, id: true,
createdAt: true, createdAt: true,
nominalCair: true, nominalCair: true,
title: true,
}, },
}); });
} else if (category === "get-one") { } else if (category === "get-one") {

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"; import { prisma } from "@/lib";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET }; export { GET };
@@ -9,7 +10,7 @@ async function GET(req: Request, { params }: { params: { id: string } }) {
const { searchParams } = new URL(req.url); const { searchParams } = new URL(req.url);
const page = searchParams.get("page"); const page = searchParams.get("page");
const status = searchParams.get("status"); const status = searchParams.get("status");
const takeData = 10; const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = Number(page) * takeData - takeData; const skipData = Number(page) * takeData - takeData;
const fixStatus = _.startCase(status || ""); const fixStatus = _.startCase(status || "");

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 }; export { GET };
@@ -9,11 +10,10 @@ async function GET(request: Request) {
const category = searchParams.get("category"); const category = searchParams.get("category");
const page = searchParams.get("page"); const page = searchParams.get("page");
const search = searchParams.get("search"); const search = searchParams.get("search");
const takeData = 10; const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = Number(page) * takeData - takeData; const skipData = Number(page) * takeData - takeData;
console.log("[CATEGORY]", category);
let fixData; let fixData;
try { try {
if (category === "dashboard") { if (category === "dashboard") {
const publish = await prisma.donasi.count({ const publish = await prisma.donasi.count({
@@ -48,7 +48,7 @@ async function GET(request: Request) {
where: { where: {
active: true, active: true,
}, },
} },
); );
const categoryDonation = countCategoryDonation.length; const categoryDonation = countCategoryDonation.length;
@@ -68,7 +68,6 @@ async function GET(request: Request) {
}, },
}); });
console.log("[STATUS]", checkStatus);
if (!checkStatus) { if (!checkStatus) {
return NextResponse.json( return NextResponse.json(
@@ -77,7 +76,7 @@ async function GET(request: Request) {
message: "Failed to get data donation", message: "Failed to get data donation",
reason: "Status not found", reason: "Status not found",
}, },
{ status: 500 } { status: 500 },
); );
} }
@@ -100,6 +99,12 @@ async function GET(request: Request) {
select: { select: {
id: true, id: true,
title: true, title: true,
target: true,
DonasiMaster_Durasi: {
select: {
name: true,
},
},
Author: { Author: {
select: { select: {
id: true, id: true,
@@ -109,7 +114,6 @@ async function GET(request: Request) {
}, },
}); });
console.log("[LIST]", fixData);
} }
return NextResponse.json( return NextResponse.json(
@@ -118,7 +122,7 @@ async function GET(request: Request) {
message: `Success get data donation ${category}`, message: `Success get data donation ${category}`,
data: fixData, data: fixData,
}, },
{ status: 200 } { status: 200 },
); );
} catch (error) { } catch (error) {
console.error("Error get data donation:", error); console.error("Error get data donation:", error);
@@ -128,7 +132,7 @@ async function GET(request: Request) {
message: "Failed to get data donation", message: "Failed to get data donation",
reason: (error as Error).message, reason: (error as Error).message,
}, },
{ status: 500 } { status: 500 },
); );
} }
} }

View File

@@ -1,9 +1,15 @@
import { NextResponse } from "next/server"; import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
export { GET }; export { GET };
async function GET(request: Request, { params }: { params: { id: string } }) { async function GET(request: Request, { params }: { params: { id: string } }) {
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
try { try {
const { id } = params; const { id } = params;
@@ -12,6 +18,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
eventId: id, eventId: id,
}, },
select: { select: {
id: true,
eventId: true, eventId: true,
userId: true, userId: true,
isPresent: true, isPresent: true,
@@ -35,6 +42,8 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
}, },
}, },
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
}); });
return NextResponse.json( return NextResponse.json(

View File

@@ -1,7 +1,8 @@
import _ from "lodash";
import { prisma } from "@/lib"; import { prisma } from "@/lib";
import { NextResponse } from "next/server"; import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import _ from "lodash";
import moment from "moment"; import moment from "moment";
import { NextResponse } from "next/server";
export { GET }; export { GET };
@@ -11,13 +12,12 @@ async function GET(request: Request) {
const fixStatus = _.startCase(category || ""); const fixStatus = _.startCase(category || "");
const search = searchParams.get("search"); const search = searchParams.get("search");
const page = searchParams.get("page"); const page = Number(searchParams.get("page")) || 1;
const takeData = 10; const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = Number(page) * takeData - takeData; const skipData = page * takeData - takeData;
let fixData; let fixData;
console.log("[CATEGORY]", category);
// console.log("[FIX STATUS]", fixStatus);
try { try {
if (category === "dashboard") { if (category === "dashboard") {
@@ -71,7 +71,6 @@ async function GET(request: Request) {
typeOfEvent, typeOfEvent,
}; };
} else if (category === "history") { } else if (category === "history") {
console.log("[HISTORY HERE]");
const data = await prisma.event.findMany({ const data = await prisma.event.findMany({
take: page ? takeData : undefined, take: page ? takeData : undefined,
@@ -151,21 +150,22 @@ async function GET(request: Request) {
}, },
}, },
select: { select: {
id: true, id: true,
title: true, title: true,
tanggal: true, tanggal: true,
Author: { tanggalSelesai: true,
select: { Author: {
id: true, select: {
username: true, id: true,
Profile: { username: true,
select: { Profile: {
name: true, select: {
}, name: true,
}, },
}, },
}, },
}, },
},
}); });
fixData = data; fixData = data;
@@ -177,7 +177,7 @@ async function GET(request: Request) {
message: `Success get data event ${category}`, message: `Success get data event ${category}`,
data: fixData, data: fixData,
}, },
{ status: 200 } { status: 200 },
); );
} catch (error) { } catch (error) {
console.log(`[ERROR GET DATA EVENT: ${category}]`, error); console.log(`[ERROR GET DATA EVENT: ${category}]`, error);
@@ -187,7 +187,7 @@ async function GET(request: Request) {
message: `Error get data event ${category}`, message: `Error get data event ${category}`,
reason: (error as Error).message, reason: (error as Error).message,
}, },
{ 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"; import { prisma } from "@/lib";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET }; export { GET };
@@ -8,6 +9,9 @@ async function GET(request: Request, { params }: { params: { name: string } }) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const category = searchParams.get("category"); const category = searchParams.get("category");
const search = searchParams.get("search"); const search = searchParams.get("search");
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
let fixData; let fixData;
try { try {
@@ -66,6 +70,8 @@ async function GET(request: Request, { params }: { params: { name: string } }) {
title: true, title: true,
Author: true, Author: true,
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
}); });
} }

View File

@@ -1,14 +1,22 @@
import { prisma } from "@/lib"; import { prisma } from "@/lib";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
export { GET, POST }; export { GET, POST };
async function GET() { async function GET(request: Request) {
try { try {
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
const data = await prisma.masterBank.findMany({ const data = await prisma.masterBank.findMany({
orderBy: { orderBy: {
updatedAt: "desc", updatedAt: "desc",
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
}); });
return NextResponse.json( return NextResponse.json(

View File

@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib"; import { prisma } from "@/lib";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import { NextResponse } from "next/server";
export { GET, PUT }; export { GET, PUT };
@@ -11,6 +12,10 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
const category = searchParams.get("category"); const category = searchParams.get("category");
const subBidangId = searchParams.get("subBidangId"); const subBidangId = searchParams.get("subBidangId");
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
if (category === "all") { if (category === "all") {
const bidang = await prisma.masterBidangBisnis.findUnique({ const bidang = await prisma.masterBidangBisnis.findUnique({
where: { where: {
@@ -45,6 +50,16 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
}, },
}); });
fixData = subBidang;
} else if (category === "only-sub-bidang") {
const subBidang = await prisma.masterSubBidangBisnis.findMany({
where: {
masterBidangBisnisId: id,
},
take: takeData,
skip: skipData,
});
fixData = subBidang; fixData = subBidang;
} }
@@ -71,9 +86,6 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const category = searchParams.get("category"); const category = searchParams.get("category");
console.log("category", category);
console.log("data", data);
try { try {
if (category === "bidang") { if (category === "bidang") {
const updateData = await prisma.masterBidangBisnis.update({ const updateData = await prisma.masterBidangBisnis.update({

View File

@@ -2,15 +2,24 @@ import { NextResponse } from "next/server";
import { prisma } from "@/lib"; import { prisma } from "@/lib";
import _ from "lodash"; import _ from "lodash";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET, POST }; export { GET, POST };
async function GET(request: Request) { async function GET(request: Request) {
try { try {
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
const data = await prisma.masterBidangBisnis.findMany({ const data = await prisma.masterBidangBisnis.findMany({
orderBy: { orderBy: {
createdAt: "asc", createdAt: "asc",
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
}); });
return NextResponse.json({ return NextResponse.json({

View File

@@ -1,10 +1,14 @@
import { NextResponse } from "next/server"; import { NextRequest, 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, POST }; export { GET, POST };
async function GET(request: Request) { async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
// const category = searchParams.get("category"); // const category = searchParams.get("category");
let fixData; let fixData;
@@ -13,6 +17,8 @@ async function GET(request: Request) {
orderBy: { orderBy: {
createdAt: "asc", createdAt: "asc",
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
}); });
// if (category === "category") { // if (category === "category") {

View File

@@ -1,14 +1,22 @@
import { NextResponse } from "next/server"; import { NextRequest, 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, POST }; export { GET, POST };
async function GET(request: Request) { async function GET(request: NextRequest) {
try { try {
const searchParams = request.nextUrl.searchParams;
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
const data = await prisma.eventMaster_TipeAcara.findMany({ const data = await prisma.eventMaster_TipeAcara.findMany({
orderBy: { orderBy: {
updatedAt: "desc", updatedAt: "desc",
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
}); });
return NextResponse.json({ return NextResponse.json({

View File

@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib"; import { prisma } from "@/lib";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import { NextResponse } from "next/server";
export { GET }; export { GET };
@@ -7,10 +8,16 @@ async function GET(request: Request) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const search = searchParams.get("search"); const search = searchParams.get("search");
const category = searchParams.get("category"); const category = searchParams.get("category");
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
console.log("SEARCH", search);
console.log("PAGE", page);
let fixData; let fixData;
try { try {
if(category === "only-user"){ if (category === "only-user") {
fixData = await prisma.user.findMany({ fixData = await prisma.user.findMany({
orderBy: { orderBy: {
updatedAt: "desc", updatedAt: "desc",
@@ -22,8 +29,10 @@ async function GET(request: Request) {
mode: "insensitive", mode: "insensitive",
}, },
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
}); });
} else if(category === "only-admin"){ } else if (category === "only-admin") {
fixData = await prisma.user.findMany({ fixData = await prisma.user.findMany({
orderBy: { orderBy: {
updatedAt: "desc", updatedAt: "desc",
@@ -35,8 +44,10 @@ async function GET(request: Request) {
mode: "insensitive", mode: "insensitive",
}, },
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
}); });
} else if (category === "all-role"){ } else if (category === "all-role") {
fixData = await prisma.user.findMany({ fixData = await prisma.user.findMany({
orderBy: { orderBy: {
updatedAt: "desc", updatedAt: "desc",
@@ -48,13 +59,15 @@ async function GET(request: Request) {
}, },
{ {
masterUserRoleId: "2", masterUserRoleId: "2",
} },
], ],
username: { username: {
contains: search || "", contains: search || "",
mode: "insensitive", mode: "insensitive",
}, },
}, },
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
}); });
} }
@@ -65,13 +78,11 @@ async function GET(request: Request) {
data: fixData, data: fixData,
}); });
} catch (error) { } catch (error) {
return NextResponse.json( return NextResponse.json({
{ status: 500,
status: 500, success: false,
success: false, message: "Error get data user access",
message: "Error get data user access", reason: (error as Error).message,
reason: (error as Error).message, });
},
);
} }
} }

View File

@@ -2,6 +2,7 @@ import _ from "lodash";
import moment from "moment"; import moment from "moment";
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 };
@@ -12,7 +13,7 @@ async function GET(request: Request) {
const search = searchParams.get("search"); const search = searchParams.get("search");
const page = searchParams.get("page"); const page = searchParams.get("page");
const takeData = 10; const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = Number(page) * takeData - takeData; const skipData = Number(page) * takeData - takeData;
let fixData; let fixData;

View File

@@ -84,7 +84,7 @@ export async function GET(req: Request) {
}, },
{ status: 500 } { status: 500 }
); );
} finally {
await prisma.$disconnect();
} }
// Removed prisma.$disconnect() from here to prevent connection pool exhaustion
// Prisma connections are handled globally and shouldn't be disconnected on each request
} }

View File

@@ -28,6 +28,7 @@ import { useRouter } from "next/navigation";
import { IconMoodSmileFilled } from "@tabler/icons-react"; import { IconMoodSmileFilled } from "@tabler/icons-react";
import { listStiker } from "../../lib/stiker"; import { listStiker } from "../../lib/stiker";
import { UIGlobal_Modal } from "../../ui"; import { UIGlobal_Modal } from "../../ui";
import mqtt_client from "@/util/mqtt_client";
const ReactQuill = dynamic( const ReactQuill = dynamic(
async () => { async () => {
@@ -248,10 +249,12 @@ function ButtonAction({ value, lengthData }: ButtonActionProps) {
ComponentGlobal_NotifikasiBerhasil(create.message); ComponentGlobal_NotifikasiBerhasil(create.message);
router.back(); router.back();
mqtt_client.publish( if (typeof window !== 'undefined' && mqtt_client) {
"Forum_create_new", mqtt_client.publish(
JSON.stringify({ isNewPost: true, count: 1 }) "Forum_create_new",
); JSON.stringify({ isNewPost: true, count: 1 })
);
}
} else { } else {
ComponentGlobal_NotifikasiGagal(create.message); ComponentGlobal_NotifikasiGagal(create.message);
} }

View File

@@ -9,30 +9,21 @@ declare global {
let prisma: PrismaClient; let prisma: PrismaClient;
if (process.env.NODE_ENV === "production") { if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient(); prisma = new PrismaClient({
// Reduce logging in production to improve performance
log: ['error', 'warn'],
});
} else { } else {
if (!global.prisma) { if (!global.prisma) {
global.prisma = new PrismaClient(); global.prisma = new PrismaClient({
log: ['error', 'warn', 'info', 'query'], // More verbose logging in development
});
} }
prisma = global.prisma; prisma = global.prisma;
} }
// Tambahkan listener hanya jika belum ditambahkan sebelumnya // Tambahkan listener hanya jika belum ditambahkan sebelumnya
if (!global.prismaListenersAdded) { if (!global.prismaListenersAdded) {
// Handle uncaught errors
process.on("uncaughtException", async (error) => {
console.error("Uncaught Exception:", error);
await prisma.$disconnect();
process.exit(1);
});
// Handle unhandled promise rejections
process.on("unhandledRejection", async (error) => {
console.error("Unhandled Rejection:", error);
await prisma.$disconnect();
process.exit(1);
});
// Handle graceful shutdown // Handle graceful shutdown
process.on("SIGINT", async () => { process.on("SIGINT", async () => {
console.log("Received SIGINT signal. Closing database connections..."); console.log("Received SIGINT signal. Closing database connections...");
@@ -51,3 +42,4 @@ if (!global.prismaListenersAdded) {
} }
export default prisma; export default prisma;
export { prisma };

24
src/lib/prismaUtils.ts Normal file
View File

@@ -0,0 +1,24 @@
import { prisma } from './prisma';
/**
* Utility function to safely execute Prisma operations
* This prevents improper disconnection of the Prisma client
* which was causing high CPU usage and connection pool issues
*/
export async function executeDbOperation<T>(
operation: () => Promise<T>,
errorMessage: string = "Database operation failed"
): Promise<{ success: boolean; data?: T; error?: string }> {
try {
const data = await operation();
return { success: true, data };
} catch (error) {
console.error(errorMessage, error);
return { success: false, error: (error as Error).message };
}
// Note: We intentionally do NOT call prisma.$disconnect() here
// Prisma manages connection pooling automatically and disconnecting
// on each request causes performance issues
}
export { prisma };

View File

@@ -66,9 +66,10 @@ export const middleware = async (req: NextRequest) => {
const { pathname } = req.nextUrl; const { pathname } = req.nextUrl;
const apiBaseUrl = new URL(req.url).origin || process.env.NEXT_PUBLIC_API_URL; const apiBaseUrl = new URL(req.url).origin || process.env.NEXT_PUBLIC_API_URL;
const dbUrl = process.env.DATABASE_URL; // Removed excessive logging that was causing high CPU usage
console.log("DATABASE_URL >>", dbUrl); // const dbUrl = process.env.DATABASE_URL;
console.log("URL Access >>", req.url); // console.log("DATABASE_URL >>", dbUrl);
// console.log("URL Access >>", req.url);
// Handle CORS preflight // Handle CORS preflight
const corsResponse = handleCors(req); const corsResponse = handleCors(req);

View File

@@ -4,7 +4,66 @@ declare global {
var mqtt_client: mqtt.MqttClient; var mqtt_client: mqtt.MqttClient;
} }
const mqtt_client = // Initialize MQTT client with proper error handling and reconnection settings
globalThis.mqtt_client || mqtt.connect("wss://io.wibudev.com"); let mqtt_client: mqtt.MqttClient;
if (typeof window === 'undefined') {
// Server-side code
mqtt_client = globalThis.mqtt_client || (() => {
const client = mqtt.connect("wss://io.wibudev.com", {
reconnectPeriod: 5000, // Reconnect every 5 seconds
connectTimeout: 30 * 1000, // 30 second timeout
// Clean session to avoid message queue buildup
clean: true,
// Reduce unnecessary pings
keepalive: 60
});
// Prevent multiple initializations
globalThis.mqtt_client = client;
// Add error handling
client.on('error', (error) => {
console.error('MQTT Connection Error:', error);
});
client.on('reconnect', () => {
console.log('MQTT Reconnecting...');
});
client.on('close', () => {
console.log('MQTT Connection Closed');
});
return client;
})();
} else {
// Client-side code - initialize only once
if (!(globalThis as any).mqtt_client) {
(globalThis as any).mqtt_client = mqtt.connect("wss://io.wibudev.com", {
reconnectPeriod: 5000, // Reconnect every 5 seconds
connectTimeout: 30 * 1000, // 30 second timeout
// Clean session to avoid message queue buildup
clean: true,
// Reduce unnecessary pings
keepalive: 60
});
// Add error handling
(globalThis as any).mqtt_client.on('error', (error: any) => {
console.error('MQTT Connection Error:', error);
});
(globalThis as any).mqtt_client.on('reconnect', () => {
console.log('MQTT Reconnecting...');
});
(globalThis as any).mqtt_client.on('close', () => {
console.log('MQTT Connection Closed');
});
}
mqtt_client = (globalThis as any).mqtt_client;
}
export default mqtt_client; export default mqtt_client;

View File

@@ -3,20 +3,27 @@
import { useEffect } from "react"; import { useEffect } from "react";
import mqtt_client from "./mqtt_client"; import mqtt_client from "./mqtt_client";
export default function MqttLoader() { export default function MqttLoader() {
useEffect(() => { useEffect(() => {
mqtt_client.on("connect", () => { // Only set up connection handlers once
console.log("connected"); const handleConnect = () => {
}); console.log("MQTT connected");
};
const handleError = (error: any) => {
console.error("MQTT Error:", error);
};
// Subscribe to events
mqtt_client.on("connect", handleConnect);
mqtt_client.on("error", handleError);
// Cleanup function to unsubscribe when component unmounts
return () => {
mqtt_client.off("connect", handleConnect);
mqtt_client.off("error", handleError);
};
}, []); }, []);
return null; return null;
// <>
// <Stack>
// <Button onClick={onClick}>Tekan</Button>
// <Button onClick={onClick2}>Tekan 2</Button>
// </Stack>
// </>
// );
} }