Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 107 additions & 22 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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);
}
Comment on lines +70 to +74
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typeof window === "undefined" check is unnecessary in a server component. This code will always run on the server side, so the window check can be removed.

Suggested change
if (typeof window === "undefined") {
// Log detailed error server-side
// eslint-disable-next-line no-console
console.error("Database error:", dbError);
}
// Log detailed error server-side
// eslint-disable-next-line no-console
console.error("Database error:", dbError);

Copilot uses AI. Check for mistakes.
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-center p-8 max-w-md mx-auto">
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-6">
<div className="flex items-center justify-center w-12 h-12 mx-auto mb-4 bg-yellow-100 rounded-full">
<svg
className="w-6 h-6 text-yellow-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg>
</div>
<h3 className="text-lg font-medium text-yellow-800 mb-2">
Database Not Available
</h3>
<p className="text-yellow-700 mb-4">
Sorry, something went wrong connecting to the database.
</p>
<div className="text-left bg-yellow-100 rounded p-3 text-sm text-yellow-800">
<p className="font-medium mb-1">To fix this:</p>
<ol className="list-decimal list-inside space-y-1">
<li>
Set the{" "}
<code className="bg-yellow-200 px-1 rounded">
DATABASE_URL
</code>{" "}
environment variable
</li>
<li>Ensure your database server is running</li>
<li>Verify database connection credentials</li>
<li>Run database migrations if needed</li>
</ol>
</div>
</div>
</div>
</div>
);
}

// 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 {
Expand All @@ -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,
Expand Down
81 changes: 81 additions & 0 deletions prisma/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,87 @@ declare const globalThis: {
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & 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<boolean> => {
// 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();
Comment on lines +39 to +42
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a new PrismaClient instance for each availability check is inefficient and could lead to connection pool exhaustion. Consider reusing the existing prisma instance or implementing a connection pooling strategy for health checks.

Suggested change
// Use a temporary PrismaClient instance for the availability check
const tempPrisma = new PrismaClient();
await tempPrisma.$queryRaw`SELECT 1`;
await tempPrisma.$disconnect();
// Use the shared PrismaClient instance for the availability check
await prisma.$queryRaw`SELECT 1`;
// No need to disconnect the shared PrismaClient instance here
// (prisma.$disconnect() is handled on app shutdown)

Copilot uses AI. Check for mistakes.

// 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 <T>(
operation: () => Promise<T>,
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;
Expand Down