diff --git a/app/page.tsx b/app/page.tsx index b05c0ac..1716154 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,113 @@ 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) { + if (typeof window === "undefined") { + // Log detailed error server-side + // eslint-disable-next-line no-console + console.error("Database error:", dbError); + } + return ( +
+
+
+
+ + + +
+

+ Database Not Available +

+

+ Sorry, something went wrong connecting to the database. +

+
+

To fix this:

+
    +
  1. + Set the{" "} + + DATABASE_URL + {" "} + environment variable +
  2. +
  3. Ensure your database server is running
  4. +
  5. Verify database connection credentials
  6. +
  7. Run database migrations if needed
  8. +
+
+
+
+
+ ); + } + + // 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 +131,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..19ba5fc 100644 --- a/prisma/db.ts +++ b/prisma/db.ts @@ -8,6 +8,87 @@ declare const globalThis: { prismaGlobal: ReturnType; } & typeof global; +// 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 +export const safeDbOperation = async ( + operation: () => Promise, + fallback: T +): Promise<{ data: T; error: string | null }> => { + 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: errorMessage, + }; + } + + 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;