diff --git a/CHANGELOG_BRANCH.md b/CHANGELOG_BRANCH.md new file mode 100644 index 00000000..8178d6db --- /dev/null +++ b/CHANGELOG_BRANCH.md @@ -0,0 +1,63 @@ +# Changelog for Branch: fixed-bug/12-feb-26 + +## 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 `prisma.$disconnect()` 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 +- `src/lib/prismaUtils.ts` - Utility functions for safe database operations + +### Modified Files +1. `src/app/api/user-validate/route.ts` + - Removed problematic `prisma.$disconnect()` call + +2. `src/lib/prisma.ts` + - Configured different logging levels for dev/prod + - Removed process listeners that were causing disconnections + - Exported prisma instance separately + +3. `src/middleware.tsx` + - Removed excessive logging statements + +4. `src/util/mqtt_client.ts` + - Enhanced initialization with error handling + - Added reconnection and timeout configurations + +5. `src/util/mqtt_loader.tsx` + - Added proper cleanup functions + - Improved connection handling + +6. `src/app_modules/_global/component/new/comp_V3_text_editor_stiker.tsx` + - 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 newline at end of file diff --git a/PROMPT-AI.md b/PROMPT-AI.md index 2dbed296..0f9bd874 100644 --- a/PROMPT-AI.md +++ b/PROMPT-AI.md @@ -1,5 +1,5 @@ -File utama: src/app/api/mobile/donation/[id]/donatur/route.ts +File utama: src/app/api/mobile/admin/user/route.ts 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 diff --git a/src/app/api/mobile/admin/user/route.ts b/src/app/api/mobile/admin/user/route.ts index ff4aabf8..ab57eb90 100644 --- a/src/app/api/mobile/admin/user/route.ts +++ b/src/app/api/mobile/admin/user/route.ts @@ -1,5 +1,6 @@ -import { NextResponse } from "next/server"; import { prisma } from "@/lib"; +import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue"; +import { NextResponse } from "next/server"; export { GET }; @@ -7,10 +8,16 @@ async function GET(request: Request) { const { searchParams } = new URL(request.url); const search = searchParams.get("search"); 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; try { - if(category === "only-user"){ + if (category === "only-user") { fixData = await prisma.user.findMany({ orderBy: { updatedAt: "desc", @@ -22,8 +29,10 @@ async function GET(request: Request) { 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({ orderBy: { updatedAt: "desc", @@ -35,8 +44,10 @@ async function GET(request: Request) { 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({ orderBy: { updatedAt: "desc", @@ -48,13 +59,15 @@ async function GET(request: Request) { }, { masterUserRoleId: "2", - } + }, ], username: { contains: search || "", mode: "insensitive", }, }, + take: page ? takeData : undefined, + skip: page ? skipData : undefined, }); } @@ -65,13 +78,11 @@ async function GET(request: Request) { data: fixData, }); } catch (error) { - return NextResponse.json( - { - status: 500, - success: false, - message: "Error get data user access", - reason: (error as Error).message, - }, - ); + return NextResponse.json({ + status: 500, + success: false, + message: "Error get data user access", + reason: (error as Error).message, + }); } } diff --git a/src/app/api/user-validate/route.ts b/src/app/api/user-validate/route.ts index a9c8fcb7..03dac63c 100644 --- a/src/app/api/user-validate/route.ts +++ b/src/app/api/user-validate/route.ts @@ -84,7 +84,7 @@ export async function GET(req: Request) { }, { 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 } diff --git a/src/app_modules/_global/component/new/comp_V3_text_editor_stiker.tsx b/src/app_modules/_global/component/new/comp_V3_text_editor_stiker.tsx index e0affdfc..755e5d48 100644 --- a/src/app_modules/_global/component/new/comp_V3_text_editor_stiker.tsx +++ b/src/app_modules/_global/component/new/comp_V3_text_editor_stiker.tsx @@ -28,6 +28,7 @@ import { useRouter } from "next/navigation"; import { IconMoodSmileFilled } from "@tabler/icons-react"; import { listStiker } from "../../lib/stiker"; import { UIGlobal_Modal } from "../../ui"; +import mqtt_client from "@/util/mqtt_client"; const ReactQuill = dynamic( async () => { @@ -248,10 +249,12 @@ function ButtonAction({ value, lengthData }: ButtonActionProps) { ComponentGlobal_NotifikasiBerhasil(create.message); router.back(); - mqtt_client.publish( - "Forum_create_new", - JSON.stringify({ isNewPost: true, count: 1 }) - ); + if (typeof window !== 'undefined' && mqtt_client) { + mqtt_client.publish( + "Forum_create_new", + JSON.stringify({ isNewPost: true, count: 1 }) + ); + } } else { ComponentGlobal_NotifikasiGagal(create.message); } diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 1620968d..bf87ef84 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -9,30 +9,21 @@ declare global { let prisma: PrismaClient; if (process.env.NODE_ENV === "production") { - prisma = new PrismaClient(); + prisma = new PrismaClient({ + // Reduce logging in production to improve performance + log: ['error', 'warn'], + }); } else { if (!global.prisma) { - global.prisma = new PrismaClient(); + global.prisma = new PrismaClient({ + log: ['error', 'warn', 'info', 'query'], // More verbose logging in development + }); } prisma = global.prisma; } // Tambahkan listener hanya jika belum ditambahkan sebelumnya 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 process.on("SIGINT", async () => { console.log("Received SIGINT signal. Closing database connections..."); @@ -51,3 +42,4 @@ if (!global.prismaListenersAdded) { } export default prisma; +export { prisma }; diff --git a/src/lib/prismaUtils.ts b/src/lib/prismaUtils.ts new file mode 100644 index 00000000..b2c473db --- /dev/null +++ b/src/lib/prismaUtils.ts @@ -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( + operation: () => Promise, + 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 }; \ No newline at end of file diff --git a/src/middleware.tsx b/src/middleware.tsx index 5c421996..89f659f3 100644 --- a/src/middleware.tsx +++ b/src/middleware.tsx @@ -66,9 +66,10 @@ export const middleware = async (req: NextRequest) => { const { pathname } = req.nextUrl; const apiBaseUrl = new URL(req.url).origin || process.env.NEXT_PUBLIC_API_URL; - const dbUrl = process.env.DATABASE_URL; - console.log("DATABASE_URL >>", dbUrl); - console.log("URL Access >>", req.url); + // Removed excessive logging that was causing high CPU usage + // const dbUrl = process.env.DATABASE_URL; + // console.log("DATABASE_URL >>", dbUrl); + // console.log("URL Access >>", req.url); // Handle CORS preflight const corsResponse = handleCors(req); diff --git a/src/util/mqtt_client.ts b/src/util/mqtt_client.ts index 5164f0da..d7b861b4 100644 --- a/src/util/mqtt_client.ts +++ b/src/util/mqtt_client.ts @@ -4,7 +4,66 @@ declare global { var mqtt_client: mqtt.MqttClient; } -const mqtt_client = - globalThis.mqtt_client || mqtt.connect("wss://io.wibudev.com"); +// Initialize MQTT client with proper error handling and reconnection settings +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; diff --git a/src/util/mqtt_loader.tsx b/src/util/mqtt_loader.tsx index eb47c73b..17a6ae9d 100644 --- a/src/util/mqtt_loader.tsx +++ b/src/util/mqtt_loader.tsx @@ -3,20 +3,27 @@ import { useEffect } from "react"; import mqtt_client from "./mqtt_client"; -export default function MqttLoader() { +export default function MqttLoader() { useEffect(() => { - mqtt_client.on("connect", () => { - console.log("connected"); - }); + // Only set up connection handlers once + 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; - - // <> - // - // - // - // - // - // ); }