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
82 changes: 82 additions & 0 deletions backend/src/config/prisma.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Prisma Client Singleton
*
* This module ensures only one instance of PrismaClient is created
* and reused across the application, preventing connection pool exhaustion.
*
* @see https://www.prisma.io/docs/guides/performance-and-optimization/connection-management
*/

import { PrismaClient } from '@prisma/client';
import { logger } from './logger';

// PrismaClient is attached to the global object in development
// to prevent hot-reloading from creating new instances
const globalForPrisma = global as unknown as { prisma: PrismaClient };

/**
* Create PrismaClient with logging configuration
*/
const createPrismaClient = (): PrismaClient => {
const client = new PrismaClient({
log: [
{
emit: 'event',
level: 'query',
},
{
emit: 'event',
level: 'error',
},
{
emit: 'event',
level: 'warn',
},
],
});

// Hook into Prisma query logging
client.$on('query' as never, ((event: any) => {
logger.debug('Prisma Query:', {
query: event.query,
params: event.params,
duration: `${event.duration}ms`,
});
}) as never);

client.$on('error' as never, ((event: any) => {
logger.error('Prisma Error:', event);
}) as never);

client.$on('warn' as never, ((event: any) => {
logger.warn('Prisma Warning:', event);
}) as never);

return client;
};

/**
* Singleton PrismaClient instance
* In development, the instance is cached on the global object
* to survive hot-reloads
*/
export const prisma = globalForPrisma.prisma || createPrismaClient();

if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}

/**
* Graceful disconnect on process termination
*/
const gracefulShutdown = async () => {
logger.info('Disconnecting Prisma Client...');
await prisma.$disconnect();
logger.info('Prisma Client disconnected');
};

process.on('beforeExit', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
process.on('SIGTERM', gracefulShutdown);

export default prisma;
4 changes: 1 addition & 3 deletions backend/src/controllers/authController.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import { PrismaClient } from '@prisma/client';
import {
generateAccessToken,
generateRefreshToken,
Expand All @@ -10,8 +9,7 @@ import { AppError, asyncHandler } from '../middleware/errorHandler';
import { logger } from '../config/logger';
import { config } from '../config/environment';
import { AuthenticatedRequest } from '../types';

const prisma = new PrismaClient();
import { prisma } from '../config/prisma';

/**
* @desc Register a new user
Expand Down
4 changes: 1 addition & 3 deletions backend/src/controllers/notificationController.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import { AppError, asyncHandler } from '../middleware/errorHandler';
import { logger } from '../config/logger';
import { AuthenticatedRequest } from '../types';

const prisma = new PrismaClient();
import { prisma } from '../config/prisma';

/**
* @desc Get user notifications
Expand Down
4 changes: 1 addition & 3 deletions backend/src/controllers/projectController.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import { AppError, asyncHandler } from '../middleware/errorHandler';
import { logger } from '../config/logger';
import { AuthenticatedRequest } from '../types';

const prisma = new PrismaClient();
import { prisma } from '../config/prisma';

/**
* Check if user has access to project
Expand Down
4 changes: 1 addition & 3 deletions backend/src/controllers/taskController.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import { AppError, asyncHandler } from '../middleware/errorHandler';
import { logger } from '../config/logger';
import { AuthenticatedRequest } from '../types';

const prisma = new PrismaClient();
import { prisma } from '../config/prisma';

/**
* Check if user has access to task
Expand Down
4 changes: 1 addition & 3 deletions backend/src/controllers/userController.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import { PrismaClient } from '@prisma/client';
import { AppError, asyncHandler } from '../middleware/errorHandler';
import { logger } from '../config/logger';
import { config } from '../config/environment';
import { AuthenticatedRequest } from '../types';

const prisma = new PrismaClient();
import { prisma } from '../config/prisma';

/**
* @desc Get current user profile
Expand Down
4 changes: 1 addition & 3 deletions backend/src/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { PrismaClient } from '@prisma/client';
import { config, jwtConfig } from '../config/environment';
import { logger } from '../config/logger';
import { AuthenticatedRequest, JWTPayload } from '../types';

const prisma = new PrismaClient();
import { prisma } from '../config/prisma';

/**
* JWT Authentication Middleware
Expand Down
41 changes: 36 additions & 5 deletions backend/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,44 @@ app.use('/api/tasks', authMiddleware, taskRoutes);
app.use('/api/notifications', authMiddleware, notificationRoutes);

// Socket.IO for real-time features
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication error: Token required'));
}

// Validate JWT token
const jwt = await import('jsonwebtoken');
const { jwtConfig } = await import('./config/environment');
const { prisma } = await import('./config/prisma');

try {
const decoded = jwt.verify(token, jwtConfig.secret, {
issuer: jwtConfig.issuer,
audience: jwtConfig.audience,
}) as any;

// Verify user exists and is active
const user = await prisma.user.findUnique({
where: { id: decoded.userId },
select: { id: true, email: true, isActive: true },
});

if (!user || !user.isActive) {
return next(new Error('Authentication error: Invalid user'));
}

// Attach user to socket
socket.data.user = user;
next();
} catch (error) {
return next(new Error('Authentication error: Invalid token'));
}
} catch (error) {
logger.error('Socket.IO auth error:', error);
return next(new Error('Authentication error'));
}
// Add token validation here
next();
});

io.on('connection', (socket) => {
Expand Down
Loading