From 4b41f9d35a0c51b525116f8e902a03b8903e6ec5 Mon Sep 17 00:00:00 2001 From: fbrcode Date: Wed, 8 Oct 2025 17:02:54 -0300 Subject: [PATCH 1/2] feat: add enhanced db settings UI info --- app/page.tsx | 124 ++++++++++++++++++++++++++++++++++++++++++--------- prisma/db.ts | 30 +++++++++++++ 2 files changed, 132 insertions(+), 22 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index b05c0ac..1da9f01 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,7 +1,7 @@ import DashChart from "@/components/DashChart"; import DashRecentTickets from "@/components/DashRecentTickets"; import TicketPieChart from "@/components/TicketPieChart"; -import prisma from "@/prisma/db"; +import prisma, { safeDbOperation } from "@/prisma/db"; const STATUS_COLORS = { OPEN: "#ef4444", @@ -16,18 +16,108 @@ const PRIORITY_COLORS = { }; async function Dashboard() { - const tickets = await prisma.ticket.findMany({ - where: { NOT: [{ status: "DONE" }] }, - orderBy: { updated_at: "desc" }, - skip: 0, - take: 5, - include: { user: true }, - }); + // Safely fetch tickets with fallback + const ticketsResult = await safeDbOperation( + () => + prisma.ticket.findMany({ + where: { NOT: [{ status: "DONE" }] }, + orderBy: { updated_at: "desc" }, + skip: 0, + take: 5, + include: { user: true }, + }), + [] + ); - const groupTickets = await prisma.ticket.groupBy({ - by: ["status"], - _count: { id: true }, - }); + // Safely fetch group tickets for chart data + const groupTicketsResult = await safeDbOperation( + () => + prisma.ticket.groupBy({ + by: ["status"], + _count: { id: true }, + }), + [] + ); + + // Safely fetch status distribution + const statusResult = await safeDbOperation( + () => + prisma.ticket.groupBy({ + by: ["status"], + _count: true, + }), + [] + ); + + // Safely fetch priority distribution + const priorityResult = await safeDbOperation( + () => + prisma.ticket.groupBy({ + by: ["priority"], + _count: true, + }), + [] + ); + + // Check if any database operation failed + const dbError = + ticketsResult.error || + groupTicketsResult.error || + statusResult.error || + priorityResult.error; + + if (dbError) { + return ( +
+
+
+
+ + + +
+

+ Database Not Connected +

+

+ The application could not connect to the database. Please check + your database configuration. +

+
+

To fix this:

+
    +
  1. + Set the{" "} + + DATABASE_URL + {" "} + environment variable +
  2. +
  3. Ensure your database is running
  4. +
  5. Run database migrations if needed
  6. +
+
+
+
+
+ ); + } + + // Process data if database operations were successful + const tickets = ticketsResult.data; + const groupTickets = groupTicketsResult.data; + const statusDistribution = statusResult.data; + const priorityDistribution = priorityResult.data; const data = groupTickets.map((item) => { return { @@ -36,16 +126,6 @@ async function Dashboard() { }; }); - const statusDistribution = await prisma.ticket.groupBy({ - by: ["status"], - _count: true, - }); - - const priorityDistribution = await prisma.ticket.groupBy({ - by: ["priority"], - _count: true, - }); - const statusData = statusDistribution.map((item) => ({ name: item.status, value: item._count, diff --git a/prisma/db.ts b/prisma/db.ts index e5d7482..7e16d7e 100644 --- a/prisma/db.ts +++ b/prisma/db.ts @@ -8,6 +8,36 @@ declare const globalThis: { prismaGlobal: ReturnType; } & typeof global; +// Check if DATABASE_URL is available +export const isDatabaseAvailable = () => { + return !!process.env.DATABASE_URL; +}; + +// Safe database operation wrapper +export const safeDbOperation = async ( + operation: () => Promise, + fallback: T +): Promise<{ data: T; error: string | null }> => { + if (!isDatabaseAvailable()) { + return { + data: fallback, + error: + "Database connection not configured. Please set DATABASE_URL environment variable.", + }; + } + + try { + const data = await operation(); + return { data, error: null }; + } catch (error) { + console.error("Database operation failed:", error); + return { + data: fallback, + error: error instanceof Error ? error.message : "Unknown database error", + }; + } +}; + const prisma = globalThis.prismaGlobal ?? prismaClientSingleton(); export default prisma; From f868427b145e7ff0594d0e68a8d7e3c9d3719522 Mon Sep 17 00:00:00 2001 From: fbrcode Date: Wed, 8 Oct 2025 17:52:25 -0300 Subject: [PATCH 2/2] feat: enhance database availability check and security --- app/page.tsx | 13 +++++++---- prisma/db.ts | 63 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 1da9f01..1716154 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -67,6 +67,11 @@ async function Dashboard() { priorityResult.error; if (dbError) { + if (typeof window === "undefined") { + // Log detailed error server-side + // eslint-disable-next-line no-console + console.error("Database error:", dbError); + } return (
@@ -87,11 +92,10 @@ async function Dashboard() {

- Database Not Connected + Database Not Available

- The application could not connect to the database. Please check - your database configuration. + Sorry, something went wrong connecting to the database.

To fix this:

@@ -103,7 +107,8 @@ async function Dashboard() { {" "} environment variable -
  • Ensure your database is running
  • +
  • Ensure your database server is running
  • +
  • Verify database connection credentials
  • Run database migrations if needed
  • diff --git a/prisma/db.ts b/prisma/db.ts index 7e16d7e..19ba5fc 100644 --- a/prisma/db.ts +++ b/prisma/db.ts @@ -8,9 +8,55 @@ declare const globalThis: { prismaGlobal: ReturnType; } & typeof global; -// Check if DATABASE_URL is available -export const isDatabaseAvailable = () => { - return !!process.env.DATABASE_URL; +// Cache database availability check to avoid repeated connection attempts +let dbAvailabilityCache: { available: boolean; lastChecked: number } | null = + null; +const DB_CHECK_CACHE_DURATION = 30000; // 30 seconds + +// Clear the database availability cache (useful for testing or manual refresh) +export const clearDatabaseAvailabilityCache = (): void => { + dbAvailabilityCache = null; +}; + +// Check if DATABASE_URL is available and database is reachable +export const isDatabaseAvailable = async (): Promise => { + // First check if DATABASE_URL is set + if (!process.env.DATABASE_URL) { + return false; + } + + // Check cache first + const now = Date.now(); + if ( + dbAvailabilityCache && + now - dbAvailabilityCache.lastChecked < DB_CHECK_CACHE_DURATION + ) { + return dbAvailabilityCache.available; + } + + try { + // Attempt to connect and perform a simple query + // Use a temporary PrismaClient instance for the availability check + const tempPrisma = new PrismaClient(); + await tempPrisma.$queryRaw`SELECT 1`; + await tempPrisma.$disconnect(); + + // Cache successful result + dbAvailabilityCache = { available: true, lastChecked: now }; + return true; + } catch (error) { + console.error( + "Database connectivity check failed:", + error instanceof Error ? error.message : JSON.stringify(error) + ); + + // Cache failed result for a shorter duration to allow retries + dbAvailabilityCache = { + available: false, + lastChecked: now - (DB_CHECK_CACHE_DURATION - 5000), + }; + return false; + } }; // Safe database operation wrapper @@ -18,11 +64,16 @@ export const safeDbOperation = async ( operation: () => Promise, fallback: T ): Promise<{ data: T; error: string | null }> => { - if (!isDatabaseAvailable()) { + const dbAvailable = await isDatabaseAvailable(); + + if (!dbAvailable) { + const errorMessage = !process.env.DATABASE_URL + ? "Database connection not configured. Please set DATABASE_URL environment variable." + : "Database is not reachable. Please check your database connection and ensure the database server is running."; + return { data: fallback, - error: - "Database connection not configured. Please set DATABASE_URL environment variable.", + error: errorMessage, }; }