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
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.sendStatus(404);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Issue: Returning status 404 for missing parameters is not correct. According to REST conventions, 404 is for 'Not Found' (i.e., resource does not exist), not for missing or invalid input. You should return status 400 (Bad Request) instead.

}

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,
};
51 changes: 51 additions & 0 deletions src/controllers/rooms.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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.sendStatus(404);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Issue: Returning status 404 for missing 'title' or 'userId' is not correct. According to REST conventions, 404 is for 'Not Found' resources, not for invalid or missing input. Use status 400 (Bad Request) instead.

}

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.sendStatus(404);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Issue: Returning status 404 for missing 'roomId' or both 'title' and 'description' is not correct. 404 is for missing resources, not invalid input. Use status 400 (Bad Request) for input validation errors.

}

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.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.sendStatus(404);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Issue: Returning status 404 for missing 'name' is not correct. 404 is for 'Not Found' resources, not for invalid or missing input. Use status 400 (Bad Request) instead.

}

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,
};
10 changes: 10 additions & 0 deletions src/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_PASS,
database: process.env.DB_DATABASE,
dialect: 'postgres',
});
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.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 || 5000;
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.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { DataTypes } from 'sequelize';
import { client } from '../db.js';
import { User } from './user.js';
import { Room } from './room.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.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DataTypes } from 'sequelize';
import { client } from '../db.js';
import { User } from './user.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.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DataTypes } from 'sequelize';
import { client } from '../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.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.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.js';

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

export const userService = {
createUser,
};