From 68ec7e094c8c70b3bbe905420cf2d1653675dedb Mon Sep 17 00:00:00 2001 From: Stas Borychevskyi Date: Thu, 18 Sep 2025 18:04:06 +0300 Subject: [PATCH 1/3] SOlution 1.0 --- src/controllers/message.controller.js | 0 src/controllers/room.controller.js | 132 ++++++++++++++++++++++++++ src/controllers/user.controller.js | 76 +++++++++++++++ src/createServer.js | 31 ++++++ src/data/associations.js | 12 +++ src/data/message.js | 29 ++++++ src/data/room.js | 22 +++++ src/data/user.js | 22 +++++ src/db/db.init.js | 17 ++++ src/db/db.js | 10 ++ src/index.js | 30 ++++++ src/messageServer.js | 27 ++++++ src/midlewares/isAuth.js | 17 ++++ src/midlewares/isNotAuth.js | 17 ++++ src/midlewares/isUserInRoom.js | 19 ++++ src/routes/room.router.js | 36 +++++++ src/routes/user.router.js | 12 +++ src/services/message.service.js | 44 +++++++++ src/services/room.service.js | 36 +++++++ src/services/user.service.js | 64 +++++++++++++ src/utils/catchError.js | 9 ++ 21 files changed, 662 insertions(+) create mode 100644 src/controllers/message.controller.js create mode 100644 src/controllers/room.controller.js create mode 100644 src/controllers/user.controller.js create mode 100644 src/createServer.js create mode 100644 src/data/associations.js create mode 100644 src/data/message.js create mode 100644 src/data/room.js create mode 100644 src/data/user.js create mode 100644 src/db/db.init.js create mode 100644 src/db/db.js create mode 100644 src/messageServer.js create mode 100644 src/midlewares/isAuth.js create mode 100644 src/midlewares/isNotAuth.js create mode 100644 src/midlewares/isUserInRoom.js create mode 100644 src/routes/room.router.js create mode 100644 src/routes/user.router.js create mode 100644 src/services/message.service.js create mode 100644 src/services/room.service.js create mode 100644 src/services/user.service.js create mode 100644 src/utils/catchError.js diff --git a/src/controllers/message.controller.js b/src/controllers/message.controller.js new file mode 100644 index 000000000..e69de29bb diff --git a/src/controllers/room.controller.js b/src/controllers/room.controller.js new file mode 100644 index 000000000..ba2622925 --- /dev/null +++ b/src/controllers/room.controller.js @@ -0,0 +1,132 @@ +import { messageService } from '../services/message.service.js'; +import { roomService } from '../services/room.service.js'; +import { Message } from '../data/message.js'; +import { userService } from '../services/user.service.js'; + +const getRoomInfoByRoomId = async (req, res) => { + const { roomId } = req.params; + const room = await roomService.findRoomById(roomId); + const messages = await messageService.findRoomsMessages(roomId); + + if (!roomId || !room) { + return res.status(404).json({ message: 'Room not Found' }); + } + + if (!messages) { + return res.status(400).json({ message: 'Cannot get messages' }); + } + + const roomInfo = { + room: { + id: room.id, + name: room.name, + }, + messages, + }; + + if (!roomInfo) { + return res.status(400).json({ message: 'Cannot transfer rooms info' }); + } + + return res.status(200).json(roomInfo); +}; + +const createRoom = async (req, res) => { + const { name } = req.body; + const user = req.user; + + if (!name) { + return res.status(400).json({ message: 'Room name is required' }); + } + + const newRoom = await roomService.createRoom(name); + + if (!newRoom) { + return res.status(400).json({ message: 'Cannot create the room' }); + } + + await newRoom.addUser(user); + + const roomUsers = await newRoom.getUsers(); + const roomInfo = { + room: { id: newRoom.id, name: newRoom.name }, + members: roomUsers, + }; + + return res.status(201).json(roomInfo); +}; + +const deleteRoom = async (req, res) => { + const { roomId } = req.params; + const room = await roomService.findRoomById(roomId); + + if (!roomId || !room) { + return res.status(404).json({ message: 'Room not Found' }); + } + + await room.setUsers([]); + await Message.destroy({ where: { roomId: room.id } }); + await room.destroy(); + + res.status(200).json({ message: 'Room deleted successfully' }); +}; + +const renameRoom = async (req, res) => { + const room = req.room; + const { newName } = req.body; + + if (!room) { + return res.status(404).json({ message: 'Room not Found' }); + } + + if (!newName || typeof newName !== 'string' || !newName.trim()) { + return res.status(400).json({ message: 'Invalid room name provided' }); + } + + room.name = newName.trim(); + await room.save(); + + return res.status(200).json(room); +}; + +const mergeRooms = async (req, res) => { + const currentRoom = req.room; + const user = req.user; + const { targetRoomId } = req.body; + + const usersRooms = await roomService.findRoomsByUserId(user.id); + const targetRoom = usersRooms.find( + (r) => r.id === parseInt(targetRoomId, 10), + ); + + if (!currentRoom) { + return res.status(404).json({ error: 'Room not found' }); + } + + if (!targetRoomId) { + return res.status(400).json({ error: 'No targetRoomId provided' }); + } + + if (!targetRoom) { + return res.status(403).json({ error: 'The user doesnt have access' }); + } + + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + await userService.mergeUsers(currentRoom.id, targetRoomId); + await messageService.mergeMessages(currentRoom.id, targetRoomId); + await currentRoom.setUsers([]); + await currentRoom.destroy(); + + return res.status(200).json(targetRoom); +}; + +export const roomController = { + getRoomInfoByRoomId, + createRoom, + deleteRoom, + renameRoom, + mergeRooms, +}; diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js new file mode 100644 index 000000000..17ccb09e5 --- /dev/null +++ b/src/controllers/user.controller.js @@ -0,0 +1,76 @@ +import { roomService } from '../services/room.service.js'; +import { userService } from '../services/user.service.js'; + +const createUser = async (req, res) => { + const { name } = req.body; + + if (!name) { + return res.status(400).json({ message: 'Name is required' }); + } + + const newUser = await userService.createUser(name); + + res.status(201).json(newUser); +}; + +const getUserInfo = async (req, res) => { + const { userId } = req.params; + + if (!userId) { + return res.status(400).json({ message: 'Id is required' }); + } + + const user = await userService.getUserById(userId); + + if (!user) { + return res.status(404).json({ message: 'User not found' }); + } + + const rooms = await roomService.findRoomsByUserId(user.id); + const userInfo = { + user: { id: user.id, name: user.name, createdAt: user.createdAt }, + rooms, + }; + + res.status(200).json(userInfo); +}; + +const changeUserName = async (req, res) => { + const user = req.user; + const { newName } = req.body; + + if (!user) { + return res.status(404).json({ message: 'User not Found' }); + } + + if (!newName) { + return res.status(400).json({ message: 'No new name provided' }); + } + + user.name = newName; + + await user.save(); + + res.status(200).json({ id: user.id, userName: user.name }); +}; + +const deleteUser = async (req, res) => { + const user = req.user; + + if (!user) { + return res.status(404).json({ message: 'User not Found' }); + } + + await user.destroy(); + + req.user = null; + + res.status(200).json({ message: 'User deleted successfully' }); +}; + +export const userController = { + createUser, + getUserInfo, + changeUserName, + deleteUser, +}; diff --git a/src/createServer.js b/src/createServer.js new file mode 100644 index 000000000..6063390c9 --- /dev/null +++ b/src/createServer.js @@ -0,0 +1,31 @@ +import express from 'express'; +import cors from 'cors'; + +import { userRouter } from './routes/user.router.js'; +import { roomRouter } from './routes/room.router.js'; + +export function createServer() { + const server = express(); + + server.use(express.json()); + + server.use( + cors({ + origin: process.env.CLIENT_ORIGIN || 'http://localhost:3000', + credentials: true, + }), + ); + + server.get('/', (req, res) => { + res.sendStatus(200).json({ message: 'Server is running' }); + }); + + server.use('/user', userRouter); + server.use('/room', roomRouter); + + server.use((req, res, next) => { + res.status(404).json({ message: 'Route not found' }); + }); + + return server; +} diff --git a/src/data/associations.js b/src/data/associations.js new file mode 100644 index 000000000..9a377d3f1 --- /dev/null +++ b/src/data/associations.js @@ -0,0 +1,12 @@ +import { User } from './user.js'; +import { Message } from './message.js'; +import { Room } from './room.js'; + +User.hasMany(Message, { foreignKey: 'userId' }); +Message.belongsTo(User, { foreignKey: 'userId' }); + +Room.hasMany(Message, { foreignKey: 'roomId' }); +Message.belongsTo(Room, { foreignKey: 'roomId' }); + +User.belongsToMany(Room, { through: 'user_room', foreignKey: 'userId' }); +Room.belongsToMany(User, { through: 'user_room', foreignKey: 'roomId' }); diff --git a/src/data/message.js b/src/data/message.js new file mode 100644 index 000000000..8acedae35 --- /dev/null +++ b/src/data/message.js @@ -0,0 +1,29 @@ +import { DataTypes } from 'sequelize'; +import { client } from '../db/db.js'; + +export const Message = client.define( + 'Message', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + roomId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + text: { + type: DataTypes.STRING, + allowNull: false, + }, + }, + { + timestamps: true, + tableName: 'messages', + }, +); diff --git a/src/data/room.js b/src/data/room.js new file mode 100644 index 000000000..e5c6a4da4 --- /dev/null +++ b/src/data/room.js @@ -0,0 +1,22 @@ +import { DataTypes } from 'sequelize'; +import { client } from '../db/db.js'; + +export const Room = client.define( + 'Room', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + }, + { + timestamps: true, + tableName: 'rooms', + }, +); diff --git a/src/data/user.js b/src/data/user.js new file mode 100644 index 000000000..c5beef6c0 --- /dev/null +++ b/src/data/user.js @@ -0,0 +1,22 @@ +import { DataTypes } from 'sequelize'; +import { client } from '../db/db.js'; + +export const User = client.define( + 'User', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + }, + { + timestamps: true, + tableName: 'users', + }, +); diff --git a/src/db/db.init.js b/src/db/db.init.js new file mode 100644 index 000000000..dbc961069 --- /dev/null +++ b/src/db/db.init.js @@ -0,0 +1,17 @@ +import { client } from './db.js'; +import '../data/associations.js'; + +export const dbInit = async () => { + await client.authenticate(); + // eslint-disable-next-line no-console + console.log('✅ Database connected'); + + await client.sync({ alter: true }); + // eslint-disable-next-line no-console + console.log('✅ Tables synced'); + + const tables = await client.getQueryInterface().showAllTables(); + + // eslint-disable-next-line no-console + console.log(tables); +}; diff --git a/src/db/db.js b/src/db/db.js new file mode 100644 index 000000000..b80224efd --- /dev/null +++ b/src/db/db.js @@ -0,0 +1,10 @@ +import { Sequelize } from 'sequelize'; + +export const client = new Sequelize({ + database: 'postgres', + username: 'postgres', + host: 'localhost', + dialect: 'postgres', + port: 5432, + password: 'Rerlol100', +}); diff --git a/src/index.js b/src/index.js index ad9a93a7c..7aaad7e01 100644 --- a/src/index.js +++ b/src/index.js @@ -1 +1,31 @@ 'use strict'; + +import { createServer } from './createServer.js'; +import http from 'http'; +import { Server as SocketIOServer } from 'socket.io'; +import { messageServer } from './messageServer.js'; +import { dbInit } from './db/db.init.js'; + +async function start() { + try { + await dbInit(); + + const app = createServer(); + const httpServer = http.createServer(app); + const io = new SocketIOServer(httpServer, { + cors: { origin: '*', credentials: true }, + }); + + messageServer(io); + + httpServer.listen(3000, () => { + // eslint-disable-next-line no-console + console.log('Server running on port 3000'); + }); + } catch (err) { + // eslint-disable-next-line no-console + console.error('❌ Failed to start server:', err); + } +} + +start(); diff --git a/src/messageServer.js b/src/messageServer.js new file mode 100644 index 000000000..59d89293f --- /dev/null +++ b/src/messageServer.js @@ -0,0 +1,27 @@ +import { messageService } from './services/message.service.js'; + +export const messageServer = (io) => { + io.on('connection', (socket) => { + // eslint-disable-next-line no-console + console.log('Новий користувач підключився:', socket.id); + + socket.on('joinRoom', ({ roomId }) => { + socket.join(`room_${roomId}`); + }); + + socket.on('newMessage', async ({ roomId, userId, text }) => { + const message = await messageService.createMessage({ + roomId, + userId, + text, + }); + + io.to(`room_${roomId}`).emit('messageBroadcast', message); + }); + + socket.on('disconnect', () => { + // eslint-disable-next-line no-console + console.log('Користувач відключився:', socket.id); + }); + }); +}; diff --git a/src/midlewares/isAuth.js b/src/midlewares/isAuth.js new file mode 100644 index 000000000..134a72686 --- /dev/null +++ b/src/midlewares/isAuth.js @@ -0,0 +1,17 @@ +import { userService } from '../services/user.service.js'; + +export const isAuth = async (req, res, next) => { + const userName = req.headers['x-username']; + + if (!userName) { + return res.status(401).json({ error: 'Користувач не авторизований' }); + } + + const trueUser = await userService.getUserByName(userName); + + if (!trueUser) { + return res.status(401).json({ error: 'Користувач не авторизований' }); + } + req.user = trueUser; + next(); +}; diff --git a/src/midlewares/isNotAuth.js b/src/midlewares/isNotAuth.js new file mode 100644 index 000000000..7a3e2fc57 --- /dev/null +++ b/src/midlewares/isNotAuth.js @@ -0,0 +1,17 @@ +import { userService } from '../services/user.service.js'; + +export const isNotAuth = async (req, res, next) => { + const userName = req.headers['x-username']; + + if (!userName) { + return next(); + } + + const trueUser = await userService.getUserByName(userName); + + if (trueUser) { + return res.status(401).json({ error: 'Користувач авторизований' }); + } + + next(); +}; diff --git a/src/midlewares/isUserInRoom.js b/src/midlewares/isUserInRoom.js new file mode 100644 index 000000000..8116a62e2 --- /dev/null +++ b/src/midlewares/isUserInRoom.js @@ -0,0 +1,19 @@ +import { roomService } from '../services/room.service.js'; + +export const isUserInRoom = async (req, res, next) => { + const user = req.user; + const { roomId } = req.params; + const usersRooms = await roomService.findRoomsByUserId(user.id); + + const room = usersRooms.find((r) => r.id === parseInt(roomId, 10)); + + if (!room) { + return res + .status(401) + .json({ error: 'Користувач не має доступа до кімнати' }); + } + + req.room = room; + + next(); +}; diff --git a/src/routes/room.router.js b/src/routes/room.router.js new file mode 100644 index 000000000..2c83619ec --- /dev/null +++ b/src/routes/room.router.js @@ -0,0 +1,36 @@ +import express from 'express'; +import { isAuth } from '../midlewares/isAuth.js'; +import { isUserInRoom } from '../midlewares/isUserInRoom.js'; +import { catchError } from '../utils/catchError.js'; +import { roomController } from '../controllers/room.controller.js'; + +export const roomRouter = express.Router(); + +roomRouter.get( + '/:roomId', + isAuth, + isUserInRoom, + catchError(roomController.getRoomInfoByRoomId), +); +roomRouter.post('/:roomId', isAuth, catchError(roomController.createRoom)); + +roomRouter.delete( + '/:roomId', + isAuth, + isUserInRoom, + catchError(roomController.deleteRoom), +); + +roomRouter.patch( + '/:roomId', + isAuth, + isUserInRoom, + catchError(roomController.renameRoom), +); + +roomRouter.post( + '/:roomId/merge', + isAuth, + isUserInRoom, + catchError(roomController.mergeRooms), +); diff --git a/src/routes/user.router.js b/src/routes/user.router.js new file mode 100644 index 000000000..28c93ba7f --- /dev/null +++ b/src/routes/user.router.js @@ -0,0 +1,12 @@ +import express from 'express'; +import { userController } from '../controllers/user.controller.js'; +import { catchError } from '../utils/catchError.js'; +import { isAuth } from '../midlewares/isAuth.js'; +import { isNotAuth } from '../midlewares/isNotAuth.js'; + +export const userRouter = express.Router(); + +userRouter.post('/', isNotAuth, catchError(userController.createUser)); +userRouter.get('/:userId', isAuth, catchError(userController.getUserInfo)); +userRouter.patch('/:userId', isAuth, catchError(userController.changeUserName)); +userRouter.delete('/:userId', isAuth, catchError(userController.deleteUser)); diff --git a/src/services/message.service.js b/src/services/message.service.js new file mode 100644 index 000000000..09cef9a5e --- /dev/null +++ b/src/services/message.service.js @@ -0,0 +1,44 @@ +import { Message } from '../data/message.js'; + +const findRoomsMessages = async (roomId) => { + const messages = await Message.findAll({ where: { roomId } }); + + if (!messages) { + return []; + } + + return messages; +}; + +const mergeMessages = async (roomId, targetRoomId) => { + const messages = await findRoomsMessages(roomId); + + if (!messages || !messages.length) { + return { moved: 0 }; + } + + for (const msg of messages) { + msg.roomId = targetRoomId; + await msg.save(); + } + + return { moved: messages.length, into: targetRoomId }; +}; + +const sendMessage = async (roomId, userId, text) => { + const message = { + text: text.trim(), + userId: userId, + roomId: roomId, + }; + + await Message.create(message); + + return message; +}; + +export const messageService = { + findRoomsMessages, + mergeMessages, + sendMessage, +}; diff --git a/src/services/room.service.js b/src/services/room.service.js new file mode 100644 index 000000000..17e0fdcdb --- /dev/null +++ b/src/services/room.service.js @@ -0,0 +1,36 @@ +import { User } from '../data/user.js'; +import { Room } from '../data/room.js'; + +const findRoomsByUserId = async (userId) => { + const user = await User.findByPk(userId, { + include: Room, + }); + + if (!user) { + return []; + } + + return user.Rooms; +}; + +const findRoomById = async (roomId) => { + const room = await Room.findByPk(roomId); + + if (!room) { + return null; + } + + return room; +}; + +const createRoom = async (name) => { + const newRoom = Room.create({ name }); + + return newRoom; +}; + +export const roomService = { + findRoomsByUserId, + findRoomById, + createRoom, +}; diff --git a/src/services/user.service.js b/src/services/user.service.js new file mode 100644 index 000000000..3ffa2bd54 --- /dev/null +++ b/src/services/user.service.js @@ -0,0 +1,64 @@ +import { User } from '../data/user.js'; +import { Room } from '../data/room.js'; +import { roomService } from './room.service.js'; + +const createUser = async (name) => { + const newUser = await User.create({ + name, + }); + + return newUser; +}; + +const getUserById = async (id) => { + const user = await User.findOne({ where: { id } }); + + return user; +}; + +const getUserByName = async (name) => { + const user = await User.findOne({ where: { name } }); + + return user; +}; + +const findUsersByRoomId = async (roomId) => { + const room = await Room.findByPk(roomId, { + include: User, + }); + + if (!room) { + return []; + } + + return room.Users; +}; + +const mergeUsers = async (roomId, targetRoomId) => { + const room = await roomService.findRoomById(roomId); + const targetRoom = await roomService.findRoomById(targetRoomId); + const usersToMerge = await findUsersByRoomId(roomId); + const targetUsers = await findUsersByRoomId(targetRoomId); + + if (!room || !targetRoom) { + throw new Error('One or both rooms not found'); + } + + const targetUserIds = new Set(targetUsers.map((u) => u.id)); + + for (const user of usersToMerge) { + if (!targetUserIds.has(user.id)) { + await targetRoom.addUser(user); + } + } + + return { merged: usersToMerge.length, into: targetRoomId }; +}; + +export const userService = { + createUser, + getUserById, + getUserByName, + findUsersByRoomId, + mergeUsers, +}; diff --git a/src/utils/catchError.js b/src/utils/catchError.js new file mode 100644 index 000000000..0e1e7d8f5 --- /dev/null +++ b/src/utils/catchError.js @@ -0,0 +1,9 @@ +export const catchError = (action) => { + return async function (req, res, next) { + try { + await action(req, res, next); + } catch (error) { + next(error); + } + }; +}; From 7939b4862fa2d8618c526bfd3176cf7e13c946ee Mon Sep 17 00:00:00 2001 From: Stas Borychevskyi Date: Fri, 19 Sep 2025 14:21:18 +0300 Subject: [PATCH 2/3] Solution 1.1 --- src/controllers/room.controller.js | 45 ++++++++++++++++++++++-------- src/createServer.js | 2 +- src/messageServer.js | 17 +++++++---- src/routes/room.router.js | 5 +++- src/services/message.service.js | 39 ++++++++++++++++++-------- src/services/room.service.js | 11 +++++++- src/services/user.service.js | 7 +++++ 7 files changed, 94 insertions(+), 32 deletions(-) diff --git a/src/controllers/room.controller.js b/src/controllers/room.controller.js index ba2622925..04e22dc31 100644 --- a/src/controllers/room.controller.js +++ b/src/controllers/room.controller.js @@ -94,39 +94,62 @@ const mergeRooms = async (req, res) => { const user = req.user; const { targetRoomId } = req.body; - const usersRooms = await roomService.findRoomsByUserId(user.id); - const targetRoom = usersRooms.find( - (r) => r.id === parseInt(targetRoomId, 10), - ); + if (!user) { + return res.status(401).json({ error: 'User not authenticated' }); + } if (!currentRoom) { - return res.status(404).json({ error: 'Room not found' }); + return res.status(404).json({ error: 'Current room not found' }); } if (!targetRoomId) { return res.status(400).json({ error: 'No targetRoomId provided' }); } - if (!targetRoom) { - return res.status(403).json({ error: 'The user doesnt have access' }); + const targetRoomIdNum = parseInt(targetRoomId, 10); + + if (Number.isNaN(targetRoomIdNum)) { + return res.status(400).json({ error: 'Invalid targetRoomId' }); } - if (!user) { - return res.status(404).json({ error: 'User not found' }); + const usersRooms = await roomService.findRoomsByUserId(user.id); + const targetRoom = usersRooms.find((r) => r.id === targetRoomIdNum); + + if (!targetRoom) { + return res + .status(403) + .json({ error: 'The user doesn’t have access to the target room' }); } - await userService.mergeUsers(currentRoom.id, targetRoomId); - await messageService.mergeMessages(currentRoom.id, targetRoomId); + await userService.mergeUsers(currentRoom.id, targetRoomIdNum); + await messageService.mergeMessages(currentRoom.id, targetRoomIdNum); + await currentRoom.setUsers([]); await currentRoom.destroy(); return res.status(200).json(targetRoom); }; +const joinRoom = async (req, res) => { + const { roomId } = req.params; + const userId = req.user.id; + + const room = await roomService.findRoomById(roomId); + + if (!room) { + return res.status(404).json({ message: 'Room not found' }); + } + + await roomService.addUserToRoom(roomId, userId); + + return res.status(200).json({ message: 'Joined room successfully' }); +}; + export const roomController = { getRoomInfoByRoomId, createRoom, deleteRoom, renameRoom, mergeRooms, + joinRoom, }; diff --git a/src/createServer.js b/src/createServer.js index 6063390c9..bfd29eafc 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -17,7 +17,7 @@ export function createServer() { ); server.get('/', (req, res) => { - res.sendStatus(200).json({ message: 'Server is running' }); + res.status(200).json({ message: 'Server is running' }); }); server.use('/user', userRouter); diff --git a/src/messageServer.js b/src/messageServer.js index 59d89293f..91b4dce0f 100644 --- a/src/messageServer.js +++ b/src/messageServer.js @@ -10,13 +10,18 @@ export const messageServer = (io) => { }); socket.on('newMessage', async ({ roomId, userId, text }) => { - const message = await messageService.createMessage({ - roomId, - userId, - text, - }); + try { + const message = await messageService.createMessage({ + roomId, + userId, + text, + }); - io.to(`room_${roomId}`).emit('messageBroadcast', message); + io.to(`room_${roomId}`).emit('messageBroadcast', message); + } catch (err) { + // eslint-disable-next-line no-console + console.error('Error in newMessage handler:', err); + } }); socket.on('disconnect', () => { diff --git a/src/routes/room.router.js b/src/routes/room.router.js index 2c83619ec..d26740a68 100644 --- a/src/routes/room.router.js +++ b/src/routes/room.router.js @@ -12,7 +12,8 @@ roomRouter.get( isUserInRoom, catchError(roomController.getRoomInfoByRoomId), ); -roomRouter.post('/:roomId', isAuth, catchError(roomController.createRoom)); + +roomRouter.post('/', isAuth, catchError(roomController.createRoom)); roomRouter.delete( '/:roomId', @@ -34,3 +35,5 @@ roomRouter.post( isUserInRoom, catchError(roomController.mergeRooms), ); + +roomRouter.post('/:roomId/join', isAuth, roomController.joinRoom); diff --git a/src/services/message.service.js b/src/services/message.service.js index 09cef9a5e..b7412a123 100644 --- a/src/services/message.service.js +++ b/src/services/message.service.js @@ -1,13 +1,22 @@ import { Message } from '../data/message.js'; +import { User } from '../data/user.js'; const findRoomsMessages = async (roomId) => { - const messages = await Message.findAll({ where: { roomId } }); + const rawMessages = await Message.findAll({ + where: { roomId }, + include: [{ model: User, attributes: ['name'] }], + order: [['createdAt', 'ASC']], + }); - if (!messages) { + if (!rawMessages || rawMessages.length === 0) { return []; } - return messages; + return rawMessages.map((message) => ({ + author: message.User.name, + text: message.text, + time: message.createdAt, + })); }; const mergeMessages = async (roomId, targetRoomId) => { @@ -25,20 +34,26 @@ const mergeMessages = async (roomId, targetRoomId) => { return { moved: messages.length, into: targetRoomId }; }; -const sendMessage = async (roomId, userId, text) => { - const message = { +const createMessage = async (roomId, userId, text) => { + const message = await Message.create({ text: text.trim(), - userId: userId, - roomId: roomId, + userId, + roomId, + }); + + const savedMessage = await Message.findByPk(message.id, { + include: [{ model: User, attributes: ['name'] }], + }); + + return { + author: savedMessage.User.name, + text: savedMessage.text, + time: savedMessage.createdAt, }; - - await Message.create(message); - - return message; }; export const messageService = { findRoomsMessages, mergeMessages, - sendMessage, + createMessage, }; diff --git a/src/services/room.service.js b/src/services/room.service.js index 17e0fdcdb..6051451ba 100644 --- a/src/services/room.service.js +++ b/src/services/room.service.js @@ -1,5 +1,6 @@ import { User } from '../data/user.js'; import { Room } from '../data/room.js'; +import { userService } from './user.service.js'; const findRoomsByUserId = async (userId) => { const user = await User.findByPk(userId, { @@ -24,13 +25,21 @@ const findRoomById = async (roomId) => { }; const createRoom = async (name) => { - const newRoom = Room.create({ name }); + const newRoom = await Room.create({ name }); return newRoom; }; +const addUserToRoom = async (userId, roomId) => { + const room = await roomService.findRoomById(roomId); + const user = await userService.getUserById(userId); + + await room.addUser(user); +}; + export const roomService = { findRoomsByUserId, findRoomById, createRoom, + addUserToRoom, }; diff --git a/src/services/user.service.js b/src/services/user.service.js index 3ffa2bd54..1c6c3f5be 100644 --- a/src/services/user.service.js +++ b/src/services/user.service.js @@ -55,10 +55,17 @@ const mergeUsers = async (roomId, targetRoomId) => { return { merged: usersToMerge.length, into: targetRoomId }; }; +const getUserNameById = async (userId) => { + const user = User.findOne({ where: { userId } }); + + return user.name; +}; + export const userService = { createUser, getUserById, getUserByName, findUsersByRoomId, mergeUsers, + getUserNameById, }; From bf2905a9588cb27b28857a326387cba9d0bd5b24 Mon Sep 17 00:00:00 2001 From: Stas Borychevskyi Date: Fri, 19 Sep 2025 16:49:12 +0300 Subject: [PATCH 3/3] Solution 1.2 --- src/controllers/room.controller.js | 28 +++++++++++++--------------- src/routes/room.router.js | 24 ++++++++++++++---------- src/routes/user.router.js | 27 +++++++++++++++++++++++---- src/utils/catchError.js | 2 +- 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/controllers/room.controller.js b/src/controllers/room.controller.js index 04e22dc31..333b824d4 100644 --- a/src/controllers/room.controller.js +++ b/src/controllers/room.controller.js @@ -5,30 +5,28 @@ import { userService } from '../services/user.service.js'; const getRoomInfoByRoomId = async (req, res) => { const { roomId } = req.params; - const room = await roomService.findRoomById(roomId); - const messages = await messageService.findRoomsMessages(roomId); - if (!roomId || !room) { - return res.status(404).json({ message: 'Room not Found' }); + const id = parseInt(roomId, 10); + + if (isNaN(id)) { + return res.status(400).json({ message: 'Invalid roomId' }); } - if (!messages) { - return res.status(400).json({ message: 'Cannot get messages' }); + const room = await roomService.findRoomById(id); + + if (!room) { + return res.status(404).json({ message: 'Room not Found' }); } - const roomInfo = { + const messages = await messageService.findRoomsMessages(id); + + return res.status(200).json({ room: { id: room.id, name: room.name, }, messages, - }; - - if (!roomInfo) { - return res.status(400).json({ message: 'Cannot transfer rooms info' }); - } - - return res.status(200).json(roomInfo); + }); }; const createRoom = async (req, res) => { @@ -140,7 +138,7 @@ const joinRoom = async (req, res) => { return res.status(404).json({ message: 'Room not found' }); } - await roomService.addUserToRoom(roomId, userId); + await roomService.addUserToRoom(userId, roomId); return res.status(200).json({ message: 'Joined room successfully' }); }; diff --git a/src/routes/room.router.js b/src/routes/room.router.js index d26740a68..47b57ef7c 100644 --- a/src/routes/room.router.js +++ b/src/routes/room.router.js @@ -8,32 +8,36 @@ export const roomRouter = express.Router(); roomRouter.get( '/:roomId', - isAuth, - isUserInRoom, + catchError(isAuth), + catchError(isUserInRoom), catchError(roomController.getRoomInfoByRoomId), ); -roomRouter.post('/', isAuth, catchError(roomController.createRoom)); +roomRouter.post('/', catchError(isAuth), catchError(roomController.createRoom)); roomRouter.delete( '/:roomId', - isAuth, - isUserInRoom, + catchError(isAuth), + catchError(isUserInRoom), catchError(roomController.deleteRoom), ); roomRouter.patch( '/:roomId', - isAuth, - isUserInRoom, + catchError(isAuth), + catchError(isUserInRoom), catchError(roomController.renameRoom), ); roomRouter.post( '/:roomId/merge', - isAuth, - isUserInRoom, + catchError(isAuth), + catchError(isUserInRoom), catchError(roomController.mergeRooms), ); -roomRouter.post('/:roomId/join', isAuth, roomController.joinRoom); +roomRouter.post( + '/:roomId/join', + catchError(isAuth), + catchError(roomController.joinRoom), +); diff --git a/src/routes/user.router.js b/src/routes/user.router.js index 28c93ba7f..681ed2d3e 100644 --- a/src/routes/user.router.js +++ b/src/routes/user.router.js @@ -6,7 +6,26 @@ import { isNotAuth } from '../midlewares/isNotAuth.js'; export const userRouter = express.Router(); -userRouter.post('/', isNotAuth, catchError(userController.createUser)); -userRouter.get('/:userId', isAuth, catchError(userController.getUserInfo)); -userRouter.patch('/:userId', isAuth, catchError(userController.changeUserName)); -userRouter.delete('/:userId', isAuth, catchError(userController.deleteUser)); +userRouter.post( + '/', + catchError(isNotAuth), + catchError(userController.createUser), +); + +userRouter.get( + '/:userId', + catchError(isAuth), + catchError(userController.getUserInfo), +); + +userRouter.patch( + '/:userId', + catchError(isAuth), + catchError(userController.changeUserName), +); + +userRouter.delete( + '/:userId', + catchError(isAuth), + catchError(userController.deleteUser), +); diff --git a/src/utils/catchError.js b/src/utils/catchError.js index 0e1e7d8f5..0d7178665 100644 --- a/src/utils/catchError.js +++ b/src/utils/catchError.js @@ -1,7 +1,7 @@ export const catchError = (action) => { return async function (req, res, next) { try { - await action(req, res, next); + await action.call(this, req, res, next); } catch (error) { next(error); }