-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/sse tickets #252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat/sse tickets #252
Changes from all commits
4df71ce
97bf0ba
2e83438
5fdf0fe
24d9a7f
8cf1067
a782152
13bf344
0a28b3e
6eaf4a6
c371c55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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'] | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return io; | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export const getIO = () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!io) { | ||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('Socket.io not initialized'); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| return io; | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| 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 | ||
| }); | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.