Skip to content
Draft
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
4 changes: 4 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import mumukshuRoutes from './routes/client/mumukshuBooking.routes.js';
import paymentRoutes from './routes/client/payment.routes.js';
import supportRoutes from './routes/client/support.routes.js';
import updateRoutes from './routes/client/updates.routes.js';
import ticketRoutes from './routes/client/ticket.routes.js';

// Admin Route Imports
import authRoutes from './routes/admin/auth.routes.js';
Expand All @@ -48,6 +49,7 @@ import {
} from './routes/admin/utsavManagement.routes.js';
import avtManagementRoutes from './routes/admin/avtManagement.routes.js';
import wifiManagementRoutes from './routes/admin/wifiManagement.routes.js';
import ticketManagementRoutes from './routes/admin/ticketManagement.routes.js';

// Unified Route Imports
import unifiedBookingRoutes from './routes/client/unifiedBooking.routes.js';
Expand Down Expand Up @@ -170,6 +172,7 @@ app.use('/api/v1/profile', profileRoutes);
app.use('/api/v1/location', locationRoutes);
app.use('/api/v1/razorpay', paymentRoutes);
app.use('/api/v1/support', supportRoutes);
app.use('/api/v1/tickets', ticketRoutes);

// Admin Routes
app.use('/api/v1/admin/sudo', adminControlRoutes);
Expand All @@ -188,6 +191,7 @@ app.use('/api/v1/admin/utsav', utsavPublicRouter); // No auth
app.use('/api/v1/admin/utsav', utsavAdminRouter); // With auth
app.use('/api/v1/admin/avt', avtManagementRoutes);
app.use('/api/v1/admin/wifi', wifiManagementRoutes);
app.use('/api/v1/admin/tickets', ticketManagementRoutes);

// Unified Routes
app.use('/api/v1/unified', unifiedBookingRoutes);
Expand Down
33 changes: 33 additions & 0 deletions config/socket.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Server } from 'socket.io';

let io;

export const initSocket = (httpServer) => {
io = new Server(httpServer, {
cors: {
origin: '*', // restrict later if needed
methods: ['GET', 'POST']
}
Comment on lines +7 to +10
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-high high

The CORS configuration for socket.io is overly permissive with origin: '*'. This allows any website to connect to your socket.io server, which is a significant security risk. It is strongly recommended to restrict the origin to a specific list of allowed domains.

Suggested change
cors: {
origin: '*', // restrict later if needed
methods: ['GET', 'POST']
}
cors: {
origin: ['https://your-client-app.com', 'https://your-admin-app.com'], // TODO: Replace with actual client domains
methods: ['GET', 'POST']
}

});

io.on('connection', socket => {
console.log('Admin connected:', socket.id);

socket.on('join_admin', () => {
socket.join('admins');
});

socket.on('disconnect', () => {
console.log('Disconnected:', socket.id);
});
});
Comment on lines +13 to +23
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

console.log is used for logging. It's better to use the application-wide logger for consistency and to allow for configurable log levels and destinations in different environments. Please import and use the application's logger here.

Suggested change
io.on('connection', socket => {
console.log('Admin connected:', socket.id);
socket.on('join_admin', () => {
socket.join('admins');
});
socket.on('disconnect', () => {
console.log('Disconnected:', socket.id);
});
});
io.on('connection', socket => {
logger.info('Admin connected:', socket.id);
socket.on('join_admin', () => {
socket.join('admins');
});
socket.on('disconnect', () => {
logger.info('Disconnected:', socket.id);
});
});


return io;
};

export const getIO = () => {
if (!io) {
throw new Error('Socket.io not initialized');
}
return io;
};
21 changes: 12 additions & 9 deletions controllers/admin/auth.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ export const login = async (req, res) => {
const admin = await AdminUsers.findOne({
where: { username: username }
});
if (admin.dataValues.status === STATUS_INACTIVE)
throw new ApiError(401, 'Account Deactivated');

if (!admin) {
throw new ApiError(404, 'Invalid Username');
}


if (admin.dataValues.status === STATUS_INACTIVE)
throw new ApiError(401, 'Account Deactivated');

const roles = await AdminRoles.findAll({
attributes: ['role_name'],
where: { user_id: admin.dataValues.id, status: STATUS_ACTIVE }
Expand Down Expand Up @@ -80,21 +81,23 @@ export const createAdmin = async (req, res) => {
return res.status(201).send({ message: 'successfully created admin' });
};



export const resetPassword = async (req, res) => {
const { username, newPassword } = req.body;

if (!username || !newPassword) {
return res.status(400).json({ message: 'Username and new password are required' });
if (!username || !newPassword) {
return res
.status(400)
.json({ message: 'Username and new password are required' });
}

if (newPassword.length < 8) {
return res.status(400).json({ message: 'Password must be at least 8 characters long' });
return res
.status(400)
.json({ message: 'Password must be at least 8 characters long' });
}

try {
const user = await AdminUsers.findOne({ where: { username } });
const user = await AdminUsers.findOne({ where: { username } });
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
Expand Down
163 changes: 163 additions & 0 deletions controllers/admin/ticketManagement.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { Ticket, TicketMessage } from '../../models/associations.js';
import { Sequelize } from 'sequelize';
import {
MSG_FETCH_SUCCESSFUL,
MSG_UPDATE_SUCCESSFUL,
STATUS_INPROGRESS,
STATUS_OPEN
} from '../../config/constants.js';
import ticketStreamManager from '../../utils/ticketStreamManager.js';
import ApiError from '../../utils/ApiError.js';

// export const getAllTickets = async (req, res) => {
// const { status, service } = req.query;
// const where = {};

// if (status) where.status = status;
// if (service) where.service = service;

// const tickets = await Ticket.findAll({
// where,
// order: [['createdAt', 'DESC']]
// });

// res.status(200).json({
// status: 'success',
// message: MSG_FETCH_SUCCESSFUL,
// data: tickets
// });
// };
Comment on lines +12 to +29
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This large block of commented-out code should be removed. Version control history can be used to track previous implementations if needed, and removing dead code keeps the codebase clean and easier to maintain.


export const getAllTickets = async (req, res) => {
const { status, service } = req.query;
const where = {};
if (status) where.status = status;
if (service) where.service = service;

const tickets = await Ticket.findAll({
where,
attributes: {
include: [
[
Sequelize.literal(`(
SELECT MAX(createdAt)
FROM ticket_messages
WHERE ticket_messages.ticket_id = Ticket.id
)`),
'last_message_at'
]
]
},
order: [['createdAt', 'DESC']]
});

res.status(200).json({
status: 'success',
data: tickets
});
};

export const getTicketDetails = async (req, res) => {
const { id } = req.params;

const ticket = await Ticket.findByPk(id);
if (!ticket) {
throw new ApiError(404, 'Ticket not found');
}

const messages = await TicketMessage.findAll({
where: { ticket_id: id },
order: [['createdAt', 'ASC']]
});

res.status(200).json({
status: 'success',
message: MSG_FETCH_SUCCESSFUL,
data: { ...ticket.toJSON(), messages }
});
};

export const streamTicketMessages = async (req, res) => {
const { id } = req.params;

const ticket = await Ticket.findByPk(id);
if (!ticket) {
throw new ApiError(404, 'Ticket not found');
}

// Set SSE headers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();

// Add admin to manager
ticketStreamManager.addClient(id, res, 'admin');

// Initial connection message
res.write(
`data: ${JSON.stringify({
type: 'connected',
user: req.user.username
})}\n\n`
);
};

export const adminAddMessage = async (req, res) => {
const { id } = req.params;
const { message } = req.body;

if (!message) {
throw new ApiError(400, 'Message is required');
}

const ticket = await Ticket.findByPk(id);
if (!ticket) {
throw new ApiError(404, 'Ticket not found');
}

if (ticket.status === 'closed') {
throw new ApiError(400, 'ticket is closed');
}

const newMessage = await TicketMessage.create({
ticket_id: id,
sender_id: req.user.username,
sender_type: 'admin',
message
});

ticketStreamManager.broadcastMessage(id, newMessage);

// Update ticket updatedBy and status if needed
const updates = { updatedBy: req.user.username };
if (ticket.status === STATUS_OPEN) {
updates.status = STATUS_INPROGRESS;
}

await ticket.update(updates);

res.status(201).json({
status: 'success',
message: MSG_UPDATE_SUCCESSFUL,
data: newMessage
});
};

export const updateTicketStatus = async (req, res) => {
const { id } = req.params;
const { status } = req.body;

const ticket = await Ticket.findByPk(id);
if (!ticket) {
throw new ApiError(404, 'Ticket not found');
}

await ticket.update({ status, updatedBy: req.user.username });

res.status(200).json({
status: 'success',
message: MSG_UPDATE_SUCCESSFUL,
data: ticket
});
};
Loading