Skip to content
Closed
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
35 changes: 35 additions & 0 deletions src/controllers/message.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { EventEmitter } from 'events';
import { messageService } from '../services/message.service.js';

export const messageEmitter = new EventEmitter();

const create = async (req, res) => {
const { roomId } = req.params;
const { text, userId } = req.body;

if (!roomId || !userId || !text) {
return res.status(400).json({ message: 'Message is required' });
}

const newMessage = await messageService.createMessageInRoom(
text,
userId,
roomId,
);

messageEmitter.emit('message', newMessage);

res.status(201).json(newMessage);
};

export const getMessages = async (req, res) => {
const { roomId } = req.params;
const messages = await messageService.getAllMessagesInRoom(roomId);

res.status(200).json(messages);
};

export const messageController = {
create,
getMessages,
};
56 changes: 56 additions & 0 deletions src/controllers/rooms.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { roomsService } from '../services/rooms.service.js';

const getAllRooms = async (req, res) => {
const rooms = await roomsService.getAllRooms();

res.status(200).send(rooms);
};

const createRoom = async (req, res) => {
const { title, userId, description } = req.body;

if (!title || !userId) {
return res
.status(400)
.json({ message: 'Missing required fields: title or userId' });
}

await roomsService.createRoom(title, userId, description);

res.sendStatus(201);
};

const updateRoom = async (req, res) => {
const { roomId } = req.params;
const { title, description } = req.body;

if ((!title && !description) || !roomId) {
return res.status(400).json({
message:
'Missing required fields: roomId and either title or description',
});
}

await roomsService.updateRoom(roomId, title, description);

res.sendStatus(204);
};

const deleteRoom = async (req, res) => {
const { roomId } = req.params;

if (!roomId) {
return res.sendStatus(400);
}

await roomsService.deleteRoom(roomId);

res.sendStatus(204);
};

export const roomController = {
getAllRooms,
createRoom,
updateRoom,
deleteRoom,
};
23 changes: 23 additions & 0 deletions src/controllers/user.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { User } from '../models/user.model.js';
import { userService } from '../services/user.service.js';

const create = async (req, res) => {
const { name } = req.body;
const isNameExist = await User.findOne({ where: { name } });

if (!name) {
return res.status(400).json({ message: 'User is required' });
}

if (isNameExist) {
return res.status(409).json({ message: 'User already exists' });
}

await userService.createUser(name);

return res.status(201).json({ message: 'User was created!' });
};

export const userController = {
create,
};
63 changes: 63 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,64 @@
'use strict';
import 'dotenv/config';
import express from 'express';
import { WebSocketServer } from 'ws';
import cors from 'cors';
import { userRoute } from './routes/user.route.js';
import { Message } from './models/message.model.js';
import { roomsRoute } from './routes/rooms.route.js';
import { messageRoute } from './routes/message.route.js';
import { messageEmitter } from './controllers/message.controller.js';

const PORT = process.env.PORT || 3005;
const app = express();

app.use(cors());
app.use(express.json());
app.use('/user', userRoute);
app.use('/rooms', roomsRoute);
app.use(messageRoute);

const server = app.listen(PORT, () => {
// eslint-disable-next-line no-console
console.log(`Server was started on ${PORT}`);
});

const wss = new WebSocketServer({ server });

const rooms = {};

wss.on('connection', (ws) => {
ws.on('message', async (message) => {
const { roomId } = JSON.parse(message);

if (!rooms[roomId]) {
rooms[roomId] = [];
}

rooms[roomId].push(ws);

const messages = await Message.findAll({ where: { roomId } });

messages.forEach((msg) => {
ws.send(JSON.stringify(msg));
});
});

ws.on('close', () => {
for (const roomId in rooms) {
rooms[roomId] = rooms[roomId].filter((client) => client !== ws);
}
});
});

messageEmitter.on('message', async (message) => {
const { roomId } = message;

if (rooms[roomId]) {
rooms[roomId].forEach((client) => {
if (client.readyState === 1) {
client.send(JSON.stringify(message));
}
});
}
});
31 changes: 31 additions & 0 deletions src/models/message.model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { DataTypes } from 'sequelize';
import { client } from '../utils/db.js';
import { User } from './user.model.js';
import { Room } from './room.model.js';

export const Message = client.define(
'message',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true,
},
text: {
type: DataTypes.STRING,
allowNull: false,
},
createdAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
allowNull: false,
},
},
{ updatedAt: false },
);

Message.belongsTo(User);
User.hasMany(Message);
Message.belongsTo(Room);
Room.hasMany(Message);
23 changes: 23 additions & 0 deletions src/models/room.model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DataTypes } from 'sequelize';
import { client } from '../utils/db.js';
import { User } from './user.model.js';

export const Room = client.define('room', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true,
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.STRING,
allowNull: false,
},
});

Room.belongsTo(User);
User.hasMany(Room);
16 changes: 16 additions & 0 deletions src/models/user.model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DataTypes } from 'sequelize';
import { client } from '../utils/db.js';

export const User = client.define('user', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
});
7 changes: 7 additions & 0 deletions src/routes/message.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import express from 'express';
import { messageController } from '../controllers/message.controller.js';

export const messageRoute = new express.Router();

messageRoute.get('/rooms/:roomId/messages', messageController.getMessages);
messageRoute.post('/rooms/:roomId/messages', messageController.create);
9 changes: 9 additions & 0 deletions src/routes/rooms.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import express from 'express';
import { roomController } from '../controllers/rooms.controller.js';

export const roomsRoute = new express.Router();

roomsRoute.get('/', roomController.getAllRooms);
roomsRoute.post('/', roomController.createRoom);
roomsRoute.patch('/:roomId', roomController.updateRoom);
roomsRoute.delete('/:roomId', roomController.deleteRoom);
6 changes: 6 additions & 0 deletions src/routes/user.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import express from 'express';
import { userController } from '../controllers/user.controller.js';

export const userRoute = new express.Router();

userRoute.post('/', userController.create);
14 changes: 14 additions & 0 deletions src/services/message.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Message } from '../models/message.model.js';

const getAllMessagesInRoom = (roomId) => {
return Message.findAll({ where: { roomId } });
};

const createMessageInRoom = (text, userId, roomId) => {
return Message.create({ text, userId, roomId });
};

export const messageService = {
getAllMessagesInRoom,
createMessageInRoom,
};
42 changes: 42 additions & 0 deletions src/services/rooms.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Room } from '../models/room.model.js';

const getAllRooms = () => {
return Room.findAll();
};

const createRoom = (title, userId, description = '') => {
return Room.create({ title, userId, description });
};

const deleteRoom = (id) => {
return Room.destroy({ where: { id } });
};

const updateRoom = (id, title, description) => {
if (!id) {
throw new Error('Room id is required');
}

const updatedData = {};

if (title) {
updatedData.title = title;
}

if (description) {
updatedData.description = description;
}

if (Object.keys(updatedData).length === 0) {
throw new Error('At least one field must be provided');
}

return Room.update(updatedData, { where: { id } });
};

export const roomsService = {
getAllRooms,
createRoom,
deleteRoom,
updateRoom,
};
9 changes: 9 additions & 0 deletions src/services/user.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { User } from '../models/user.model.js';

const createUser = async (name) => {
return User.create({ name });
};

export const userService = {
createUser,
};
10 changes: 10 additions & 0 deletions src/utils/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'dotenv/config';
import { Sequelize } from 'sequelize';

export const client = new Sequelize({
host: process.env.DB_HOST,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
dialect: 'postgres',
});