From df5d1aad48db0aa1976aa629452292151647df46 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Tue, 3 Mar 2026 15:25:39 +0800 Subject: [PATCH 1/3] chore(release): 1.6.5 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75a5a62f..3969d4a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ 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.6.5](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.6.4...v1.6.5) (2026-03-03) + + +### Bug Fixes + +* error koneksi Prisma - DATABASE_URL tidak loaded di ([a658881](https://wibugit.wibudev.com/wibu/hipmi/commit/a6588818b5d8018b3a634e0ae0846e309569d370)) + ## [1.6.4](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.6.3...v1.6.4) (2026-03-03) ## [1.6.3](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.6.2...v1.6.3) (2026-03-03) diff --git a/package.json b/package.json index 548674ec..7cff9c0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hipmi", - "version": "1.6.4", + "version": "1.6.5", "private": true, "prisma": { "seed": "bun prisma/seed.ts" -- 2.49.1 From f64ae42825115b61ee72ad9de931aaae6d9297d2 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Tue, 3 Mar 2026 15:30:34 +0800 Subject: [PATCH 2/3] Fix Prisma MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1 fix: error koneksi Prisma dengan retry mechanism 2 3 Perubahan: 4 - src/lib/prisma.ts: Tambah retry (3x) dengan exponential backoff saat connect 5 - src/lib/prisma-retry.ts: NEW - Utility wrapper untuk retry operations 6 - src/app/api/user-validate/route.ts: Improve error logging dengan detail 7 - src/middleware.tsx: Clean up commented code 8 9 Fitur: 10 - Auto retry saat database connection gagal 11 - Explicit () di production 12 - Better error logging untuk debugging 13 - Reusable retry wrapper (withRetry, withTimeout) 14 15 Testing: 16 - Build berhasil ✅ 17 - Type checking passed ✅ 18 19 Fixes: Error in PostgreSQL connection: Error { kind: Closed, cause: None } ### No Issue --- src/app/api/user-validate/route.ts | 18 ++- src/lib/prisma-retry.ts | 188 +++++++++++++++++++++++++++++ src/lib/prisma.ts | 42 +++++++ src/middleware.tsx | 4 - 4 files changed, 245 insertions(+), 7 deletions(-) create mode 100644 src/lib/prisma-retry.ts diff --git a/src/app/api/user-validate/route.ts b/src/app/api/user-validate/route.ts index 03dac63c..36e5aa25 100644 --- a/src/app/api/user-validate/route.ts +++ b/src/app/api/user-validate/route.ts @@ -76,15 +76,27 @@ export async function GET(req: Request) { data: user, }); } catch (error) { - console.error("Error in user validation:", error); + const errorMsg = error instanceof Error ? error.message : 'Unknown error'; + const errorStack = error instanceof Error ? error.stack : 'No stack'; + + // Log detailed error for debugging + console.error("❌ [USER-VALIDATE] Error:", errorMsg); + console.error("❌ [USER-VALIDATE] Stack:", errorStack); + console.error("❌ [USER-VALIDATE] Time:", new Date().toISOString()); + + // Check if it's a database connection error + if (errorMsg.includes("Prisma") || errorMsg.includes("database") || errorMsg.includes("connection")) { + console.error("❌ [USER-VALIDATE] Database connection error detected!"); + console.error("❌ [USER-VALIDATE] DATABASE_URL exists:", !!process.env.DATABASE_URL); + } + return NextResponse.json( { success: false, message: "Terjadi kesalahan pada server", + error: process.env.NODE_ENV === 'development' ? errorMsg : 'Internal server error', }, { status: 500 } ); } - // 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/lib/prisma-retry.ts b/src/lib/prisma-retry.ts new file mode 100644 index 00000000..56664c68 --- /dev/null +++ b/src/lib/prisma-retry.ts @@ -0,0 +1,188 @@ +import { prisma } from './prisma'; + +/** + * Retry configuration for database operations + */ +interface RetryConfig { + maxRetries: number; + initialDelay: number; + maxDelay: number; + factor: number; +} + +const DEFAULT_RETRY_CONFIG: RetryConfig = { + maxRetries: 3, + initialDelay: 100, + maxDelay: 5000, + factor: 2, +}; + +/** + * Check if error is retryable (transient error) + */ +function isRetryableError(error: any): boolean { + const errorMsg = error instanceof Error ? error.message : ''; + + // Retry on connection-related errors + const retryablePatterns = [ + 'ECONNRESET', + 'ECONNREFUSED', + 'ETIMEDOUT', + 'ENOTFOUND', + 'connection closed', + 'connection terminated', + 'connection timeout', + 'socket hang up', + 'too many connections', + 'pool is full', + 'server login has been failing', + 'FATAL:', + 'PrismaClientUnknownRequestError', + ]; + + return retryablePatterns.some(pattern => + errorMsg.toLowerCase().includes(pattern.toLowerCase()) + ); +} + +/** + * Execute database operation with retry mechanism + * + * @param operation - The database operation to execute + * @param config - Retry configuration (optional) + * @param operationName - Name of the operation for logging + * + * @example + * const user = await withRetry( + * () => prisma.user.findUnique({ where: { id: '123' } }), + * undefined, + * 'findUser' + * ); + */ +export async function withRetry( + operation: () => Promise, + config?: Partial, + operationName?: string +): Promise { + const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config }; + let lastError: any; + + for (let attempt = 1; attempt <= retryConfig.maxRetries; attempt++) { + try { + const result = await operation(); + + // Log success if it was a retry + if (attempt > 1 && operationName) { + console.log(`✅ [DB-RETRY] ${operationName} succeeded after ${attempt} attempts`); + } + + return result; + } catch (error) { + lastError = error; + const errorMsg = error instanceof Error ? error.message : 'Unknown error'; + + // Check if we should retry + if (attempt < retryConfig.maxRetries && isRetryableError(error)) { + // Calculate delay with exponential backoff + jitter + const delay = Math.min( + retryConfig.initialDelay * Math.pow(retryConfig.factor, attempt - 1), + retryConfig.maxDelay + ); + const jitter = Math.random() * 0.3 * delay; // Add 30% jitter + + if (operationName) { + console.warn( + `⚠️ [DB-RETRY] ${operationName} failed (attempt ${attempt}/${retryConfig.maxRetries}): ${errorMsg}` + ); + console.log(`⏳ [DB-RETRY] Retrying in ${Math.round(delay + jitter)}ms...`); + } + + await new Promise(resolve => setTimeout(resolve, delay + jitter)); + } else { + // Don't retry - either max retries reached or not a retryable error + if (operationName) { + console.error( + `❌ [DB-RETRY] ${operationName} failed after ${attempt} attempts: ${errorMsg}` + ); + } + break; + } + } + } + + // All retries exhausted, throw the last error + throw lastError; +} + +/** + * Execute database operation with timeout + * + * @param operation - The database operation to execute + * @param timeout - Timeout in milliseconds (default: 30000) + * @param operationName - Name of the operation for logging + */ +export async function withTimeout( + operation: () => Promise, + timeout: number = 30000, + operationName?: string +): Promise { + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error(`Operation timed out after ${timeout}ms`)); + }, timeout); + }); + + try { + return await Promise.race([operation(), timeoutPromise]); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : 'Unknown error'; + if (errorMsg.includes('timed out')) { + if (operationName) { + console.error(`⏱️ [DB-TIMEOUT] ${operationName} timed out after ${timeout}ms`); + } + } + throw error; + } +} + +/** + * Combine retry and timeout for robust database operations + * + * @param operation - The database operation to execute + * @param options - Retry and timeout options + * @param operationName - Name of the operation for logging + */ +export async function withRetryAndTimeout( + operation: () => Promise, + options?: { + retry?: Partial; + timeout?: number; + }, + operationName?: string +): Promise { + return withRetry( + () => withTimeout(operation, options?.timeout, operationName), + options?.retry, + operationName + ); +} + +/** + * Health check for database connection + */ +export async function checkDatabaseConnection(): Promise { + try { + await withTimeout( + () => prisma.$queryRaw`SELECT 1`, + 5000, + 'healthCheck' + ); + return true; + } catch (error) { + const errorMsg = error instanceof Error ? error.message : 'Unknown error'; + console.error('❌ [DB-HEALTH] Database connection check failed:', errorMsg); + return false; + } +} + +export { prisma }; diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 47db3c82..77d281ac 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -36,6 +36,40 @@ if (process.env.NODE_ENV === "production") { }, }, }); + + // Explicitly connect to database dengan retry + const maxRetries = 3; + let retryCount = 0; + + const connectWithRetry = async () => { + while (retryCount < maxRetries) { + try { + await prisma.$connect(); + console.log('✅ PostgreSQL connected successfully'); + return; + } catch (error) { + retryCount++; + const errorMsg = error instanceof Error ? error.message : 'Unknown error'; + console.error(`❌ PostgreSQL connection attempt ${retryCount}/${maxRetries} failed:`, errorMsg); + + if (retryCount >= maxRetries) { + console.error('❌ All database connection attempts failed. Application will continue but database operations will fail.'); + throw error; + } + + // Wait before retry (exponential backoff) + const waitTime = Math.min(1000 * Math.pow(2, retryCount), 10000); + console.log(`⏳ Retrying in ${waitTime}ms...`); + await new Promise(resolve => setTimeout(resolve, waitTime)); + } + } + }; + + // Initialize connection (non-blocking) + connectWithRetry().catch(err => { + console.error('Failed to initialize database connection:', err); + }); + } else { if (!global.prisma) { global.prisma = new PrismaClient({ @@ -65,6 +99,14 @@ if (!global.prismaListenersAdded) { process.exit(0); }); + // Handle uncaught errors + process.on("uncaughtException", async (error) => { + if (error.message.includes("Prisma") || error.message.includes("database")) { + console.error("Uncaught database error:", error); + await prisma.$disconnect(); + } + }); + // Tandai bahwa listener sudah ditambahkan global.prismaListenersAdded = true; } diff --git a/src/middleware.tsx b/src/middleware.tsx index a96ef4cf..0d532965 100644 --- a/src/middleware.tsx +++ b/src/middleware.tsx @@ -66,10 +66,6 @@ export const middleware = async (req: NextRequest) => { const { pathname } = req.nextUrl; const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL || new URL(req.url).origin; - // 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); -- 2.49.1 From 240f6eb7c2f21d828b73a7e3d69aea823a2043f9 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Tue, 3 Mar 2026 15:34:36 +0800 Subject: [PATCH 3/3] Fix console --- scripts/postbuild.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scripts/postbuild.js b/scripts/postbuild.js index 41b26d28..170072d9 100644 --- a/scripts/postbuild.js +++ b/scripts/postbuild.js @@ -57,8 +57,4 @@ if (fs.existsSync(envLocalSrc)) { console.log('✓ .env-local file copied to standalone output'); } -console.log('✅ Postbuild script completed!'); -console.log(''); -console.log('📋 Penting untuk Production:'); -console.log(' - Pastikan DATABASE_URL tersedia di environment server, ATAU'); -console.log(' - File .env sudah ter-copy ke /app/.env di server'); +console.log('✅ Build script completed!'); \ No newline at end of file -- 2.49.1