diff --git a/src/back-end/controllers/message.controller.js b/src/back-end/controllers/message.controller.js new file mode 100644 index 000000000..40f7e59b7 --- /dev/null +++ b/src/back-end/controllers/message.controller.js @@ -0,0 +1,35 @@ +import { messagesService } from '../services/messages.service.js'; +import { EventEmitter } from 'events'; + +export const messageEmitter = new EventEmitter(); + +const findAllMessages = async (req, res) => { + const { roomId } = req.params; + const messages = await messagesService.findMessages(roomId); + + res.status(201).send(messages); +}; + +const createMessage = async (req, res) => { + const { roomId } = req.params; + const { userId, text } = req.body; + + if (!roomId || !userId || !text) { + return res.sendStatus(401); + } + + const newMessage = await messagesService.createNewMessage( + roomId, + userId, + text, + ); + + messageEmitter.emit('message', newMessage); + + res.status(200).send(newMessage); +}; + +export const messagesController = { + findAllMessages, + createMessage, +}; diff --git a/src/back-end/controllers/room.controller.js b/src/back-end/controllers/room.controller.js new file mode 100644 index 000000000..3b82741d6 --- /dev/null +++ b/src/back-end/controllers/room.controller.js @@ -0,0 +1,59 @@ +import { Room } from '../../models/Room.model.js'; +import { roomService } from '../services/room.service.js'; + +const createRoom = async (req, res) => { + const { title } = req.body; + + if (!title) { + return res.sendStatus(401); + } + + const newRoom = await roomService.createNewRoom(title); + + res.status(201).send(newRoom); +}; + +const getAllRooms = async (req, res) => { + const rooms = await Room.findAll(); + + res.status(200).send(rooms); +}; + +const deleteRoom = async (req, res) => { + const { roomId } = req.params; + + if (!roomId) { + return res.sendStatus(401); + } + + await Room.destroy({ where: { id: roomId } }); + + res.sendStatus(204); +}; + +const updateTitle = async (req, res) => { + const { roomId } = req.params; + const { newTitle } = req.body; + + if (!roomId || !newTitle) { + return res.sendStatus(401); + } + + const room = await Room.findByPk(roomId); + + if (!room) { + return res.sendStatus(404); + } + + room.title = newTitle; + await room.save(); + + res.sendStatus(200); +}; + +export const roomController = { + createRoom, + getAllRooms, + deleteRoom, + updateTitle, +}; diff --git a/src/back-end/controllers/user.controller.js b/src/back-end/controllers/user.controller.js new file mode 100644 index 000000000..44bd14e4f --- /dev/null +++ b/src/back-end/controllers/user.controller.js @@ -0,0 +1,17 @@ +import { userService } from '../services/user.service.js'; + +const newUser = async (req, res) => { + const { name } = req.body; + + if (!name) { + return res.sendStatus(401); + } + + await userService.createUser(name); + + res.sendStatus(201); +}; + +export const userController = { + newUser, +}; diff --git a/src/back-end/models/Message.route.js b/src/back-end/models/Message.route.js new file mode 100644 index 000000000..148439e82 --- /dev/null +++ b/src/back-end/models/Message.route.js @@ -0,0 +1,22 @@ +import { DataTypes } from 'sequelize'; +import { client } from '../db.js'; + +export const Message = client.define('Message', { + text: { + type: DataTypes.STRING, + allowNull: false, + }, + userId: { + type: DataTypes.STRING, + allowNull: false, + }, + createdAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + allowNull: false, + }, + roomId: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}); diff --git a/src/back-end/models/Room.model.js b/src/back-end/models/Room.model.js new file mode 100644 index 000000000..060922653 --- /dev/null +++ b/src/back-end/models/Room.model.js @@ -0,0 +1,9 @@ +import { DataTypes } from 'sequelize'; +import { client } from '../db.js'; + +export const Room = client.define('Room', { + title: { + type: DataTypes.STRING, + allowNull: false, + }, +}); diff --git a/src/back-end/models/User.model.js b/src/back-end/models/User.model.js new file mode 100644 index 000000000..428f2f7f5 --- /dev/null +++ b/src/back-end/models/User.model.js @@ -0,0 +1,9 @@ +import { DataTypes } from 'sequelize'; +import { client } from '../db.js'; + +export const User = client.define('User', { + name: { + type: DataTypes.STRING, + allowNull: false, + }, +}); diff --git a/src/back-end/routes/message.router.js b/src/back-end/routes/message.router.js new file mode 100644 index 000000000..ba488c611 --- /dev/null +++ b/src/back-end/routes/message.router.js @@ -0,0 +1,8 @@ +'use strict'; +import express from 'express'; +import { messagesController } from '../controllers/message.controller.js'; + +export const messageRouter = new express.Router(); + +messageRouter.get('/:roomId/messages', messagesController.findAllMessages); +messageRouter.post('/:roomId/messages', messagesController.createMessage); diff --git a/src/back-end/routes/room.routes.js b/src/back-end/routes/room.routes.js new file mode 100644 index 000000000..bf0350048 --- /dev/null +++ b/src/back-end/routes/room.routes.js @@ -0,0 +1,10 @@ +'use strict'; +import express from 'express'; +import { roomController } from '../controllers/room.controller.js'; + +export const roomRouter = new express.Router(); + +roomRouter.get('/', roomController.getAllRooms); +roomRouter.post('/createRoom', roomController.createRoom); +roomRouter.put('/:roomId', roomController.updateTitle); +roomRouter.delete('/:roomId', roomController.deleteRoom); diff --git a/src/back-end/routes/user.router.js b/src/back-end/routes/user.router.js new file mode 100644 index 000000000..d6c9acc6d --- /dev/null +++ b/src/back-end/routes/user.router.js @@ -0,0 +1,7 @@ +'use strict'; +import express from 'express'; +import { userController } from '../controllers/user.controller.js'; + +export const userRouter = new express.Router(); + +userRouter.post('/', userController.newUser); diff --git a/src/back-end/services/messages.service.js b/src/back-end/services/messages.service.js new file mode 100644 index 000000000..41644cd08 --- /dev/null +++ b/src/back-end/services/messages.service.js @@ -0,0 +1,14 @@ +import { Message } from '../models/Message.route.js'; + +const findMessages = async (roomId) => { + return Message.findAll({ where: { roomId } }); +}; + +const createNewMessage = (roomId, text, userId) => { + return Message.create({ roomId, text, userId }); +}; + +export const messagesService = { + findMessages, + createNewMessage, +}; diff --git a/src/back-end/services/room.service.js b/src/back-end/services/room.service.js new file mode 100644 index 000000000..0d74cbb8c --- /dev/null +++ b/src/back-end/services/room.service.js @@ -0,0 +1,9 @@ +import { Room } from '../models/Room.model.js'; + +const createNewRoom = async (title) => { + return Room.create({ title }); +}; + +export const roomService = { + createNewRoom, +}; diff --git a/src/back-end/services/user.service.js b/src/back-end/services/user.service.js new file mode 100644 index 000000000..4c57d5c5c --- /dev/null +++ b/src/back-end/services/user.service.js @@ -0,0 +1,9 @@ +import { User } from '../models/User.model.js'; + +const createUser = async (name) => { + return User.create({ name }); +}; + +export const userService = { + createUser, +}; diff --git a/src/db.js b/src/db.js new file mode 100644 index 000000000..0931c22d1 --- /dev/null +++ b/src/db.js @@ -0,0 +1,8 @@ +'use strict'; +import { Sequelize } from 'sequelize'; +import 'dotenv/config'; + +export const client = new Sequelize('postgres', 'postgres', '29052009', { + host: 'localhost', + dialect: 'postgres', +}); diff --git a/src/index.js b/src/index.js index ad9a93a7c..9cd0fa964 100644 --- a/src/index.js +++ b/src/index.js @@ -1 +1,59 @@ 'use strict'; +import express from 'express'; +import cors from 'cors'; +import { userRouter } from './back-end/routes/user.router.js'; +import { messageRouter } from './back-end/routes/message.router.js'; +import { roomRouter } from './back-end/routes/room.routes.js'; +import { WebSocketServer } from 'ws'; +import { messageEmitter } from './back-end/controllers/message.controller.js'; + +const PORT = process.env.PORT || 3005; +const app = express(); + +app.use(express.json()); + +app.use( + cors({ + origin: '*', + }), +); + +app.use('/user', userRouter); +app.use('/rooms', roomRouter); +app.use('/rooms', messageRouter); + +app.get('/', (req, res) => { + res.send('Server is OK'); +}); + +const server = app.listen(PORT, () => { + // eslint-disable-next-line no-console + console.log('server is running'); +}); + +const wss = new WebSocketServer({ server }); + +wss.on('connection', (ws) => { + ws.roomId = null; + + ws.on('message', (data) => { + try { + const parsed = JSON.parse(data); + + if (parsed.type === 'join' && parsed.roomId) { + ws.roomId = parsed.roomId; + } + } catch (error) { + console.error('Invalid message from client', error); + } + }); +}); + +messageEmitter.on('message', (message) => { + for (const client of wss.clients) { + if (client.readyState === 1 && client.roomId === message.roomId) { + client.send(JSON.stringify(message)); + } + } +}); + diff --git a/src/setup.js b/src/setup.js new file mode 100644 index 000000000..e8b0ae237 --- /dev/null +++ b/src/setup.js @@ -0,0 +1,19 @@ +import { Message } from './models/Message.route.js'; +import { Room } from './models/Room.model.js'; +import { User } from './models/User.model.js'; + +async function setup() { + try { + await User.sync({ force: true }); + await Room.sync({ force: true }); + await Message.sync({ force: true }); + + // eslint-disable-next-line no-console + console.log('Tables synced!'); + } catch (err) { + // eslint-disable-next-line no-console + console.error('Error syncing:', err); + } +} + +setup();