From 399595e319404a0b5ce2469e314e632c77af6063 Mon Sep 17 00:00:00 2001 From: Denys Kovalchuk Date: Fri, 9 May 2025 15:58:43 +0300 Subject: [PATCH 1/4] add task solution --- src/controllers/message.controller.js | 29 ++++++++++ src/controllers/room.controller.js | 76 +++++++++++++++++++++++++++ src/controllers/user.controller.js | 18 +++++++ src/exeptions/api.error.js | 32 +++++++++++ src/index.js | 36 +++++++++++++ src/middlewares/authMiddleware.js | 12 +++++ src/middlewares/errorMiddleware.js | 15 ++++++ src/models/message.js | 32 +++++++++++ src/models/room.js | 18 +++++++ src/models/user.js | 10 ++++ src/routes/message.route.js | 18 +++++++ src/routes/rooms.route.js | 20 +++++++ src/routes/user.route.js | 7 +++ src/services/message.service.js | 20 +++++++ src/services/room.service.js | 50 ++++++++++++++++++ src/services/user.service.js | 19 +++++++ src/utils/catchError.js | 9 ++++ src/utils/db.js | 11 ++++ src/utils/emmiter.js | 3 ++ src/utils/store.js | 3 ++ 20 files changed, 438 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/exeptions/api.error.js create mode 100644 src/middlewares/authMiddleware.js create mode 100644 src/middlewares/errorMiddleware.js create mode 100644 src/models/message.js create mode 100644 src/models/room.js create mode 100644 src/models/user.js create mode 100644 src/routes/message.route.js create mode 100644 src/routes/rooms.route.js create mode 100644 src/routes/user.route.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 create mode 100644 src/utils/db.js create mode 100644 src/utils/emmiter.js create mode 100644 src/utils/store.js diff --git a/src/controllers/message.controller.js b/src/controllers/message.controller.js new file mode 100644 index 000000000..003f8c592 --- /dev/null +++ b/src/controllers/message.controller.js @@ -0,0 +1,29 @@ +import { ApiError } from '../exeptions/api.error.js'; +import { Message } from '../models/message.js'; +import { messageService } from '../services/message.service.js'; + +const getAllMessages = async (req, res) => { + const { roomId } = req.params; + + const allMessages = await Message.findAll({ where: { roomId }}); + + res.send(allMessages); +} + +const createMessage = async (req, res) => { + const { text } = req.body; + const { roomId } = req.params; + + if (!text) { + throw ApiError.badRequest('Enter the message'); + } + + await messageService.createMessage(text, roomId); + + res.status(201).send(text); +}; + +export const messageController = { + getAllMessages, + createMessage, +}; diff --git a/src/controllers/room.controller.js b/src/controllers/room.controller.js new file mode 100644 index 000000000..c07248136 --- /dev/null +++ b/src/controllers/room.controller.js @@ -0,0 +1,76 @@ +import { ApiError } from '../exeptions/api.error.js'; +import { Room } from '../models/room.js'; +import { roomService } from '../services/room.service.js'; + +const getAllRooms = async (req, res) => { + const rooms = await Room.findAll(); + + res.send(rooms); +}; + +const getRoomById = async (req, res) => { + const { roomId } = req.params; + + const room = await roomService.getRoomById(roomId); + + if (!room) { + throw ApiError.notFound(); + } + + res.send(room); +}; + +const createRoom = async (req, res) => { + const { title, participants } = req.body; + + if (!title) { + throw ApiError.badRequest('Enter the title'); + } + + await roomService.createRoom(title, participants) + + res.status(201).send({ message: 'Room created'}); +}; + +const updateRoom = async (req, res) => { + const { roomId } = req.params; + const { title, participants } = req.body; + + const room = await roomService.getRoomById(roomId); + + if (!room) { + throw ApiError.notFound(); + } + + if (!Array.isArray(participants)) { + throw ApiError.badRequest('Participants shoude be an array'); + } + + await roomService.updateRoom(room, title, participants) + + const updatedRoom = await roomService.getRoomById(roomId); + + res.send(updatedRoom); +}; + +const deleteRoom = async (req, res) => { + const { roomId } = req.params; + + const room = await roomService.getRoomById(roomId); + + if (!room) { + throw ApiError.notFound(); + } + + await roomService.deleteRoom(roomId); + + res.status(204).send({ message: 'Room deleted' }); +}; + +export const roomController = { + createRoom, + updateRoom, + deleteRoom, + getAllRooms, + getRoomById, +}; diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js new file mode 100644 index 000000000..b643cec49 --- /dev/null +++ b/src/controllers/user.controller.js @@ -0,0 +1,18 @@ +import { ApiError } from '../exeptions/api.error.js'; +import { userService } from '../services/user.service.js'; + +const createUser = async (req, res) => { + const { username } = req.body; + + if (!username) { + throw ApiError.badRequest('Enter the username'); + } + + await userService.createUser(username); + + res.status(201).send({ message: 'User created'}); +}; + +export const userController = { + createUser, +}; diff --git a/src/exeptions/api.error.js b/src/exeptions/api.error.js new file mode 100644 index 000000000..0a141242e --- /dev/null +++ b/src/exeptions/api.error.js @@ -0,0 +1,32 @@ +export class ApiError extends Error { + constructor({ message, status, errors = {} }) { + super(message); + + this.status = status; + this.errors = errors; + } + + static badRequest(message, errors) { + return new ApiError({ + message, + errors, + status: 400, + }); + } + + static unauthorized(errors) { + return new ApiError({ + message: 'unauthorized user', + errors, + status: 401, + }); + } + + static notFound(errors) { + return new ApiError({ + message: 'not found', + errors, + status: 404, + }); + } +} diff --git a/src/index.js b/src/index.js index ad9a93a7c..46fefd205 100644 --- a/src/index.js +++ b/src/index.js @@ -1 +1,37 @@ 'use strict'; + +import 'dotenv/config'; +import express from 'express'; +import cors from 'cors'; +import { WebSocketServer } from 'ws'; +import { userRouter } from './routes/user.route.js'; +import { errorMiddleware } from './middlewares/errorMiddleware.js'; +import { roomsRouter } from './routes/rooms.route.js'; +import { messageRouter } from './routes/message.route.js'; +import { emmiter } from './utils/emmiter.js'; + +const PORT = process.env.PORT || 3005; + +const app = express(); + +app.use(cors()); +app.use(express.json()); + +app.use('/user', userRouter); +app.use('/rooms', roomsRouter); +app.use(messageRouter); + +app.use(errorMiddleware); + +const server = app.listen(PORT, () => { + // eslint-disable-next-line no-console + console.log('server is running'); +}); + +const wss = new WebSocketServer({ server }); + +emmiter.on('message', (message) => { + for (const client of wss.clients) { + client.send(JSON.stringify(message)); + } +}); diff --git a/src/middlewares/authMiddleware.js b/src/middlewares/authMiddleware.js new file mode 100644 index 000000000..ca47463e2 --- /dev/null +++ b/src/middlewares/authMiddleware.js @@ -0,0 +1,12 @@ +import { ApiError } from '../exeptions/api.error.js'; +import { localStorage } from '../utils/store.js'; + +export const authMiddleware = (req, res, next) => { + const user = JSON.parse(localStorage.getItem('user')); + + if (!user) { + throw ApiError.unauthorized(); + } + + next(); +}; diff --git a/src/middlewares/errorMiddleware.js b/src/middlewares/errorMiddleware.js new file mode 100644 index 000000000..ddb9ef61f --- /dev/null +++ b/src/middlewares/errorMiddleware.js @@ -0,0 +1,15 @@ +import { ApiError } from '../exeptions/api.error.js'; + +export const errorMiddleware = (error, req, res, next) => { + if (error instanceof ApiError) { + res.status(error.status).send({ + message: error.message, + errors: error.errors, + }); + + return; + } + + res.statusCode = 500; + res.send(error.message); +}; diff --git a/src/models/message.js b/src/models/message.js new file mode 100644 index 000000000..d80899dbe --- /dev/null +++ b/src/models/message.js @@ -0,0 +1,32 @@ +import { DataTypes } from 'sequelize'; +import { client } from '../utils/db.js'; +import { User } from './user.js'; +import { Room } from './room.js'; + +export const Message = client.define( + 'message', + { + text: { + type: DataTypes.STRING, + allowNull: false, + }, + author: { + type: DataTypes.STRING, + allowNull: false, + }, + time: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + allowNull: false, + }, + }, + { + createdAt: false, + updatedAt: false, + }, +); + +Message.belongsTo(User); +User.hasMany(Message); +Message.belongsTo(Room); +Room.hasMany(Message); diff --git a/src/models/room.js b/src/models/room.js new file mode 100644 index 000000000..107332dad --- /dev/null +++ b/src/models/room.js @@ -0,0 +1,18 @@ +import { DataTypes } from 'sequelize'; +import { client } from '../utils/db.js'; +import { User } from './user.js'; + +export const Room = client.define('room', { + title: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + participants: { + type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: false, + } +}); + +Room.belongsTo(User); +User.hasMany(Room); diff --git a/src/models/user.js b/src/models/user.js new file mode 100644 index 000000000..08e10d4a3 --- /dev/null +++ b/src/models/user.js @@ -0,0 +1,10 @@ +import { DataTypes } from 'sequelize'; +import { client } from '../utils/db.js'; + +export const User = client.define('user', { + username: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, +}); diff --git a/src/routes/message.route.js b/src/routes/message.route.js new file mode 100644 index 000000000..af9812239 --- /dev/null +++ b/src/routes/message.route.js @@ -0,0 +1,18 @@ +import express from 'express'; +import { catchError } from '../utils/catchError.js'; +import { messageController } from '../controllers/message.controller.js'; +import { authMiddleware } from '../middlewares/authMiddleware.js'; + +export const messageRouter = new express.Router(); + +messageRouter.get( + '/rooms/:roomId/messages', + authMiddleware, + catchError(messageController.getAllMessages) +); + +messageRouter.post( + '/rooms/:roomId/messages', + authMiddleware, + catchError(messageController.createMessage) +); diff --git a/src/routes/rooms.route.js b/src/routes/rooms.route.js new file mode 100644 index 000000000..8e968b3bf --- /dev/null +++ b/src/routes/rooms.route.js @@ -0,0 +1,20 @@ +import express from 'express'; +import { catchError } from '../utils/catchError.js'; +import { roomController } from '../controllers/room.controller.js'; +import { authMiddleware } from '../middlewares/authMiddleware.js'; + +export const roomsRouter = new express.Router(); + +roomsRouter.get('/', authMiddleware, catchError(roomController.getAllRooms)); +roomsRouter.get('/:roomId', authMiddleware, catchError(roomController.getRoomById)); +roomsRouter.post('/', authMiddleware, catchError(roomController.createRoom)); +roomsRouter.patch( + '/:roomId', + authMiddleware, + catchError(roomController.updateRoom), +); +roomsRouter.delete( + '/:roomId', + authMiddleware, + catchError(roomController.deleteRoom), +); diff --git a/src/routes/user.route.js b/src/routes/user.route.js new file mode 100644 index 000000000..65aab0e1d --- /dev/null +++ b/src/routes/user.route.js @@ -0,0 +1,7 @@ +import express from 'express'; +import { catchError } from '../utils/catchError.js'; +import { userController } from '../controllers/user.controller.js'; + +export const userRouter = new express.Router(); + +userRouter.post('/', catchError(userController.createUser)); diff --git a/src/services/message.service.js b/src/services/message.service.js new file mode 100644 index 000000000..7db2dd9f5 --- /dev/null +++ b/src/services/message.service.js @@ -0,0 +1,20 @@ +import { Message } from '../models/message.js'; +import { emmiter } from '../utils/emmiter.js'; +import { localStorage } from '../utils/store.js'; + +async function createMessage(text, roomId) { + const user = JSON.parse(localStorage.getItem('user')); + const message = await Message.create({ + text, + author: user.username, + time: new Date(), + userId: user.id, + roomId, + }); + + emmiter.emit('message', message); +} + +export const messageService = { + createMessage, +}; diff --git a/src/services/room.service.js b/src/services/room.service.js new file mode 100644 index 000000000..2a3e2ff62 --- /dev/null +++ b/src/services/room.service.js @@ -0,0 +1,50 @@ +import { ApiError } from '../exeptions/api.error.js'; +import { Room } from '../models/room.js'; +import { localStorage } from '../utils/store.js'; + +function getRoomById(roomId) { + return Room.findOne({ where: { id: roomId } }); +} + +async function createRoom(title, participants) { + const existTitle = await Room.findOne({ where: { title } }); + + if (existTitle) { + throw ApiError.badRequest('Room already exist'); + } + + const user = JSON.parse(localStorage.getItem('user')); + + await Room.create({ + title, + userId: user.id, + participants: participants + ? [user.username, ...participants] + : [user.username], + }); +} + +async function updateRoom(room, title, participants) { + const user = JSON.parse(localStorage.getItem('user')); + + if (title) { + room.title = title; + } + + if (participants) { + room.participants = [user.username, ...participants]; + } + + await room.save(); +} + +function deleteRoom(roomId) { + return Room.destroy({ where: { id: roomId } }); +} + +export const roomService = { + getRoomById, + createRoom, + updateRoom, + deleteRoom, +}; diff --git a/src/services/user.service.js b/src/services/user.service.js new file mode 100644 index 000000000..58f814a44 --- /dev/null +++ b/src/services/user.service.js @@ -0,0 +1,19 @@ +import { ApiError } from '../exeptions/api.error.js'; +import { User } from '../models/user.js'; +import { localStorage } from '../utils/store.js'; + +async function createUser(username) { + const existUser = await User.findOne({ where: { username } }); + + if (existUser) { + throw ApiError.badRequest('User already exist'); + } + + const user = await User.create({ username }); + + localStorage.setItem('user', JSON.stringify(user)); +} + +export const userService = { + createUser, +}; 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); + } + }; +}; diff --git a/src/utils/db.js b/src/utils/db.js new file mode 100644 index 000000000..d3b20989a --- /dev/null +++ b/src/utils/db.js @@ -0,0 +1,11 @@ +import { Sequelize } from 'sequelize'; + +const { DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE } = process.env; + +export const client = new Sequelize({ + host: DB_HOST || 'localhost', + username: DB_USER || 'postgres', + password: DB_PASSWORD || '12345qwert', + database: DB_DATABASE || 'postgres', + dialect: 'postgres', +}); diff --git a/src/utils/emmiter.js b/src/utils/emmiter.js new file mode 100644 index 000000000..966cd3d4f --- /dev/null +++ b/src/utils/emmiter.js @@ -0,0 +1,3 @@ +import { EventEmitter } from 'events'; + +export const emmiter = new EventEmitter(); diff --git a/src/utils/store.js b/src/utils/store.js new file mode 100644 index 000000000..a79b01c3f --- /dev/null +++ b/src/utils/store.js @@ -0,0 +1,3 @@ +import { LocalStorage } from "node-localstorage"; + +export const localStorage = new LocalStorage('./scratch'); From ebeac898da73ce23e78ba019c6a914a7f9161dad Mon Sep 17 00:00:00 2001 From: Denys Kovalchuk Date: Fri, 9 May 2025 18:33:55 +0300 Subject: [PATCH 2/4] fixed mistakes --- src/controllers/message.controller.js | 27 +++++--- src/controllers/refresh.controller.js | 28 +++++++++ src/controllers/room.controller.js | 91 ++++++++++++++++----------- src/controllers/user.controller.js | 37 +++++++++-- src/index.js | 4 ++ src/middlewares/authMiddleware.js | 20 ++++-- src/models/message.js | 3 +- src/models/room.js | 2 +- src/models/token.js | 13 ++++ src/routes/message.route.js | 4 +- src/routes/refresh.route.js | 7 +++ src/routes/rooms.route.js | 9 ++- src/services/jwt.service.js | 36 +++++++++++ src/services/message.service.js | 4 +- src/services/room.service.js | 9 +-- src/services/token.service.js | 22 +++++++ src/services/user.service.js | 28 ++++++++- src/utils/store.js | 3 - 18 files changed, 269 insertions(+), 78 deletions(-) create mode 100644 src/controllers/refresh.controller.js create mode 100644 src/models/token.js create mode 100644 src/routes/refresh.route.js create mode 100644 src/services/jwt.service.js create mode 100644 src/services/token.service.js delete mode 100644 src/utils/store.js diff --git a/src/controllers/message.controller.js b/src/controllers/message.controller.js index 003f8c592..f9076c819 100644 --- a/src/controllers/message.controller.js +++ b/src/controllers/message.controller.js @@ -1,26 +1,33 @@ import { ApiError } from '../exeptions/api.error.js'; import { Message } from '../models/message.js'; import { messageService } from '../services/message.service.js'; +import { userService } from '../services/user.service.js'; const getAllMessages = async (req, res) => { const { roomId } = req.params; - const allMessages = await Message.findAll({ where: { roomId }}); + const allMessages = await Message.findAll({ where: { roomId } }); res.send(allMessages); -} +}; -const createMessage = async (req, res) => { - const { text } = req.body; - const { roomId } = req.params; +const createMessage = async (req, res, next) => { + try { + const { text } = req.body; + const { roomId } = req.params; - if (!text) { - throw ApiError.badRequest('Enter the message'); - } + if (!text) { + throw ApiError.badRequest('Enter the message'); + } - await messageService.createMessage(text, roomId); + const user = await userService.getUser(req); - res.status(201).send(text); + await messageService.createMessage(user, text, roomId); + + res.status(201).send(text); + } catch (error) { + next(error); + } }; export const messageController = { diff --git a/src/controllers/refresh.controller.js b/src/controllers/refresh.controller.js new file mode 100644 index 000000000..1eb26db6b --- /dev/null +++ b/src/controllers/refresh.controller.js @@ -0,0 +1,28 @@ +import { ApiError } from '../exeptions/api.error.js'; +import { jwtService } from '../services/jwt.service.js'; +import { tokenService } from '../services/token.service.js'; +import { userService } from '../services/user.service.js'; +import { userController } from './user.controller.js'; + +const refresh = async (req, res, next) => { + try { + const { refreshToken } = req.cookies; + + const userData = await jwtService.verifyRefresh(refreshToken); + const token = await tokenService.getByToken(refreshToken); + + if (!userData || !token) { + throw ApiError.unauthorized(); + } + + const user = await userService.findByUserName(userData.username); + + userController.generateToken(res, user); + } catch (error) { + next(error); + } +}; + +export const refreshController = { + refresh, +}; diff --git a/src/controllers/room.controller.js b/src/controllers/room.controller.js index c07248136..cc8f964c0 100644 --- a/src/controllers/room.controller.js +++ b/src/controllers/room.controller.js @@ -1,6 +1,7 @@ import { ApiError } from '../exeptions/api.error.js'; import { Room } from '../models/room.js'; import { roomService } from '../services/room.service.js'; +import { userService } from '../services/user.service.js'; const getAllRooms = async (req, res) => { const rooms = await Room.findAll(); @@ -8,63 +9,83 @@ const getAllRooms = async (req, res) => { res.send(rooms); }; -const getRoomById = async (req, res) => { - const { roomId } = req.params; +const getRoomById = async (req, res, next) => { + try { + const { roomId } = req.params; - const room = await roomService.getRoomById(roomId); + const room = await roomService.getRoomById(roomId); - if (!room) { - throw ApiError.notFound(); - } + if (!room) { + throw ApiError.notFound(); + } - res.send(room); + res.send(room); + } catch (error) { + next(error); + } }; -const createRoom = async (req, res) => { - const { title, participants } = req.body; +const createRoom = async (req, res, next) => { + try { + const { title, participants } = req.body; - if (!title) { - throw ApiError.badRequest('Enter the title'); - } + if (!title) { + throw ApiError.badRequest('Enter the title'); + } - await roomService.createRoom(title, participants) + const user = await userService.getUser(req); - res.status(201).send({ message: 'Room created'}); + await roomService.createRoom(user, title, participants); + + res.status(201).send({ message: 'Room created' }); + } catch (error) { + next(error); + } }; -const updateRoom = async (req, res) => { - const { roomId } = req.params; - const { title, participants } = req.body; +const updateRoom = async (req, res, next) => { + try { + const { roomId } = req.params; + const { title, participants } = req.body; - const room = await roomService.getRoomById(roomId); + const room = await roomService.getRoomById(roomId); - if (!room) { - throw ApiError.notFound(); - } + if (!room) { + throw ApiError.notFound(); + } - if (!Array.isArray(participants)) { - throw ApiError.badRequest('Participants shoude be an array'); - } + if (!Array.isArray(participants)) { + throw ApiError.badRequest('Participants shoude be an array'); + } + + const user = await userService.getUser(req); - await roomService.updateRoom(room, title, participants) + await roomService.updateRoom(user, room, title, participants); - const updatedRoom = await roomService.getRoomById(roomId); + const updatedRoom = await roomService.getRoomById(roomId); - res.send(updatedRoom); + res.send(updatedRoom); + } catch (error) { + next(error); + } }; -const deleteRoom = async (req, res) => { - const { roomId } = req.params; +const deleteRoom = async (req, res, next) => { + try { + const { roomId } = req.params; - const room = await roomService.getRoomById(roomId); + const room = await roomService.getRoomById(roomId); - if (!room) { - throw ApiError.notFound(); - } + if (!room) { + throw ApiError.notFound(); + } - await roomService.deleteRoom(roomId); + await roomService.deleteRoom(roomId); - res.status(204).send({ message: 'Room deleted' }); + res.status(204).send({ message: 'Room deleted' }); + } catch (error) { + next(error); + } }; export const roomController = { diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index b643cec49..4c2007352 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -1,18 +1,43 @@ import { ApiError } from '../exeptions/api.error.js'; +import { jwtService } from '../services/jwt.service.js'; +import { tokenService } from '../services/token.service.js'; import { userService } from '../services/user.service.js'; -const createUser = async (req, res) => { - const { username } = req.body; +const createUser = async (req, res, next) => { + try { + const { username } = req.body; - if (!username) { - throw ApiError.badRequest('Enter the username'); + if (!username) { + throw ApiError.badRequest('Enter the username'); + } + + await userService.createUser(username); + + const user = await userService.findByUserName(username); + + await generateToken(res, user); + } catch (error) { + next(error); } +}; + +const generateToken = async (res, user) => { + const normalizedUser = userService.normalize(user); + + const accessToken = jwtService.sign(normalizedUser); + const refreshAccessToken = jwtService.signRefresh(normalizedUser); + + await tokenService.save(normalizedUser.id, refreshAccessToken); - await userService.createUser(username); + res.cookie('refreshToken', refreshAccessToken, { + maxAge: 30 * 24 * 60 * 60 * 1000, + httpOnly: true, + }); - res.status(201).send({ message: 'User created'}); + res.send({ user: normalizedUser, accessToken }); }; export const userController = { createUser, + generateToken, }; diff --git a/src/index.js b/src/index.js index 46fefd205..cf5e7895b 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,8 @@ import { errorMiddleware } from './middlewares/errorMiddleware.js'; import { roomsRouter } from './routes/rooms.route.js'; import { messageRouter } from './routes/message.route.js'; import { emmiter } from './utils/emmiter.js'; +import cookieParser from 'cookie-parser'; +import { refreshRouter } from './routes/refresh.route.js'; const PORT = process.env.PORT || 3005; @@ -16,10 +18,12 @@ const app = express(); app.use(cors()); app.use(express.json()); +app.use(cookieParser()); app.use('/user', userRouter); app.use('/rooms', roomsRouter); app.use(messageRouter); +app.use('/refresh', refreshRouter); app.use(errorMiddleware); diff --git a/src/middlewares/authMiddleware.js b/src/middlewares/authMiddleware.js index ca47463e2..53079efd9 100644 --- a/src/middlewares/authMiddleware.js +++ b/src/middlewares/authMiddleware.js @@ -1,11 +1,21 @@ -import { ApiError } from '../exeptions/api.error.js'; -import { localStorage } from '../utils/store.js'; +import { jwtService } from '../services/jwt.service.js'; export const authMiddleware = (req, res, next) => { - const user = JSON.parse(localStorage.getItem('user')); + const authorization = req.headers['authorization'] || ''; + const [, token] = authorization.split(' '); - if (!user) { - throw ApiError.unauthorized(); + if (!authorization || !token) { + res.sendStatus(401); + + return; + } + + const userData = jwtService.verify(token); + + if (!userData) { + res.sendStatus(401); + + return; } next(); diff --git a/src/models/message.js b/src/models/message.js index d80899dbe..f849c86af 100644 --- a/src/models/message.js +++ b/src/models/message.js @@ -21,8 +21,7 @@ export const Message = client.define( }, }, { - createdAt: false, - updatedAt: false, + timestamps: false, }, ); diff --git a/src/models/room.js b/src/models/room.js index 107332dad..1ddf8e800 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -11,7 +11,7 @@ export const Room = client.define('room', { participants: { type: DataTypes.ARRAY(DataTypes.STRING), allowNull: false, - } + }, }); Room.belongsTo(User); diff --git a/src/models/token.js b/src/models/token.js new file mode 100644 index 000000000..5e0099182 --- /dev/null +++ b/src/models/token.js @@ -0,0 +1,13 @@ +import { DataTypes } from 'sequelize'; +import { client } from '../utils/db.js'; +import { User } from './user.js'; + +export const Token = client.define('token', { + refreshToken: { + type: DataTypes.STRING, + allowNull: false, + }, +}); + +Token.belongsTo(User); +User.hasOne(Token); diff --git a/src/routes/message.route.js b/src/routes/message.route.js index af9812239..8e00d64c4 100644 --- a/src/routes/message.route.js +++ b/src/routes/message.route.js @@ -8,11 +8,11 @@ export const messageRouter = new express.Router(); messageRouter.get( '/rooms/:roomId/messages', authMiddleware, - catchError(messageController.getAllMessages) + catchError(messageController.getAllMessages), ); messageRouter.post( '/rooms/:roomId/messages', authMiddleware, - catchError(messageController.createMessage) + catchError(messageController.createMessage), ); diff --git a/src/routes/refresh.route.js b/src/routes/refresh.route.js new file mode 100644 index 000000000..8a015ec27 --- /dev/null +++ b/src/routes/refresh.route.js @@ -0,0 +1,7 @@ +import express from 'express'; +import { catchError } from '../utils/catchError.js'; +import { refreshController } from '../controllers/refresh.controller.js'; + +export const refreshRouter = new express.Router(); + +refreshRouter.get('/', catchError(refreshController.refresh)); diff --git a/src/routes/rooms.route.js b/src/routes/rooms.route.js index 8e968b3bf..a670067fb 100644 --- a/src/routes/rooms.route.js +++ b/src/routes/rooms.route.js @@ -6,13 +6,20 @@ import { authMiddleware } from '../middlewares/authMiddleware.js'; export const roomsRouter = new express.Router(); roomsRouter.get('/', authMiddleware, catchError(roomController.getAllRooms)); -roomsRouter.get('/:roomId', authMiddleware, catchError(roomController.getRoomById)); + +roomsRouter.get( + '/:roomId', + authMiddleware, + catchError(roomController.getRoomById), +); roomsRouter.post('/', authMiddleware, catchError(roomController.createRoom)); + roomsRouter.patch( '/:roomId', authMiddleware, catchError(roomController.updateRoom), ); + roomsRouter.delete( '/:roomId', authMiddleware, diff --git a/src/services/jwt.service.js b/src/services/jwt.service.js new file mode 100644 index 000000000..bc76f0fc3 --- /dev/null +++ b/src/services/jwt.service.js @@ -0,0 +1,36 @@ +import jwt from 'jsonwebtoken'; + +function sign(user) { + const token = jwt.sign(user, process.env.JWT_KEY); + + return token; +} + +function verify(token) { + try { + return jwt.verify(token, process.env.JWT_KEY); + } catch (error) { + return null; + } +} + +function signRefresh(user) { + const token = jwt.sign(user, process.env.JWT_REFRESH_KEY); + + return token; +} + +function verifyRefresh(token) { + try { + return jwt.verify(token, process.env.JWT_REFRESH_KEY); + } catch (error) { + return null; + } +} + +export const jwtService = { + sign, + verify, + signRefresh, + verifyRefresh, +}; diff --git a/src/services/message.service.js b/src/services/message.service.js index 7db2dd9f5..2c5f3683d 100644 --- a/src/services/message.service.js +++ b/src/services/message.service.js @@ -1,9 +1,7 @@ import { Message } from '../models/message.js'; import { emmiter } from '../utils/emmiter.js'; -import { localStorage } from '../utils/store.js'; -async function createMessage(text, roomId) { - const user = JSON.parse(localStorage.getItem('user')); +async function createMessage(user, text, roomId) { const message = await Message.create({ text, author: user.username, diff --git a/src/services/room.service.js b/src/services/room.service.js index 2a3e2ff62..f193c55e5 100644 --- a/src/services/room.service.js +++ b/src/services/room.service.js @@ -1,20 +1,17 @@ import { ApiError } from '../exeptions/api.error.js'; import { Room } from '../models/room.js'; -import { localStorage } from '../utils/store.js'; function getRoomById(roomId) { return Room.findOne({ where: { id: roomId } }); } -async function createRoom(title, participants) { +async function createRoom(user, title, participants) { const existTitle = await Room.findOne({ where: { title } }); if (existTitle) { throw ApiError.badRequest('Room already exist'); } - const user = JSON.parse(localStorage.getItem('user')); - await Room.create({ title, userId: user.id, @@ -24,9 +21,7 @@ async function createRoom(title, participants) { }); } -async function updateRoom(room, title, participants) { - const user = JSON.parse(localStorage.getItem('user')); - +async function updateRoom(user, room, title, participants) { if (title) { room.title = title; } diff --git a/src/services/token.service.js b/src/services/token.service.js new file mode 100644 index 000000000..25aa3c362 --- /dev/null +++ b/src/services/token.service.js @@ -0,0 +1,22 @@ +import { Token } from '../models/token.js'; + +async function save(userId, newToken) { + const token = await Token.findOne({ where: { userId } }); + + if (!token) { + await Token.create({ userId, refreshToken: newToken }); + } else { + token.refreshToken = newToken; + + await token.save(); + } +} + +function getByToken(refreshToken) { + return Token.findOne({ where: { refreshToken } }); +} + +export const tokenService = { + save, + getByToken, +}; diff --git a/src/services/user.service.js b/src/services/user.service.js index 58f814a44..bb2f163d1 100644 --- a/src/services/user.service.js +++ b/src/services/user.service.js @@ -1,6 +1,14 @@ import { ApiError } from '../exeptions/api.error.js'; import { User } from '../models/user.js'; -import { localStorage } from '../utils/store.js'; +import { jwtService } from './jwt.service.js'; + +function findByUserName(username) { + return User.findOne({ where: { username } }); +} + +function normalize({ id, username }) { + return { id, username }; +} async function createUser(username) { const existUser = await User.findOne({ where: { username } }); @@ -9,11 +17,25 @@ async function createUser(username) { throw ApiError.badRequest('User already exist'); } - const user = await User.create({ username }); + await User.create({ username }); +} + +async function getUser(req) { + const { refreshToken } = req.cookies; + const userData = await jwtService.verifyRefresh(refreshToken); + + if (!userData || !refreshToken) { + throw ApiError.unauthorized(); + } + + const user = await findByUserName(userData.username); - localStorage.setItem('user', JSON.stringify(user)); + return user; } export const userService = { + findByUserName, + normalize, createUser, + getUser, }; diff --git a/src/utils/store.js b/src/utils/store.js deleted file mode 100644 index a79b01c3f..000000000 --- a/src/utils/store.js +++ /dev/null @@ -1,3 +0,0 @@ -import { LocalStorage } from "node-localstorage"; - -export const localStorage = new LocalStorage('./scratch'); From 6f1daefb0328d9e51881b54364d1cdc0fc4d640b Mon Sep 17 00:00:00 2001 From: Denys Kovalchuk Date: Fri, 9 May 2025 18:42:42 +0300 Subject: [PATCH 3/4] fixed mistakes --- src/index.js | 2 +- src/middlewares/authMiddleware.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index cf5e7895b..4839fe1c7 100644 --- a/src/index.js +++ b/src/index.js @@ -22,7 +22,7 @@ app.use(cookieParser()); app.use('/user', userRouter); app.use('/rooms', roomsRouter); -app.use(messageRouter); +app.use('/messages', messageRouter); app.use('/refresh', refreshRouter); app.use(errorMiddleware); diff --git a/src/middlewares/authMiddleware.js b/src/middlewares/authMiddleware.js index 53079efd9..bdc6cb653 100644 --- a/src/middlewares/authMiddleware.js +++ b/src/middlewares/authMiddleware.js @@ -18,5 +18,7 @@ export const authMiddleware = (req, res, next) => { return; } + req.user = userData; + next(); }; From 2d0f8ba2ab8a30256395d38686c066ab5eda0eaa Mon Sep 17 00:00:00 2001 From: Denys Kovalchuk Date: Fri, 9 May 2025 18:57:06 +0300 Subject: [PATCH 4/4] fixed mistakes --- src/controllers/message.controller.js | 12 ++++++++---- src/controllers/room.controller.js | 10 +++++++--- src/services/user.service.js | 12 +++++++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/controllers/message.controller.js b/src/controllers/message.controller.js index f9076c819..29b8d5cbf 100644 --- a/src/controllers/message.controller.js +++ b/src/controllers/message.controller.js @@ -3,12 +3,16 @@ import { Message } from '../models/message.js'; import { messageService } from '../services/message.service.js'; import { userService } from '../services/user.service.js'; -const getAllMessages = async (req, res) => { - const { roomId } = req.params; +const getAllMessages = async (req, res, next) => { + try { + const { roomId } = req.params; - const allMessages = await Message.findAll({ where: { roomId } }); + const allMessages = await Message.findAll({ where: { roomId } }); - res.send(allMessages); + res.send(allMessages); + } catch (error) { + next(error); + } }; const createMessage = async (req, res, next) => { diff --git a/src/controllers/room.controller.js b/src/controllers/room.controller.js index cc8f964c0..d0afaaadb 100644 --- a/src/controllers/room.controller.js +++ b/src/controllers/room.controller.js @@ -3,10 +3,14 @@ import { Room } from '../models/room.js'; import { roomService } from '../services/room.service.js'; import { userService } from '../services/user.service.js'; -const getAllRooms = async (req, res) => { - const rooms = await Room.findAll(); +const getAllRooms = async (req, res, next) => { + try { + const rooms = await Room.findAll(); - res.send(rooms); + res.send(rooms); + } catch (error) { + next(error); + } }; const getRoomById = async (req, res, next) => { diff --git a/src/services/user.service.js b/src/services/user.service.js index bb2f163d1..2bdea9c3f 100644 --- a/src/services/user.service.js +++ b/src/services/user.service.js @@ -21,13 +21,19 @@ async function createUser(username) { } async function getUser(req) { - const { refreshToken } = req.cookies; - const userData = await jwtService.verifyRefresh(refreshToken); + const authorization = req.headers['authorization'] || ''; + const [, token] = authorization.split(' '); - if (!userData || !refreshToken) { + if (!authorization || !token) { throw ApiError.unauthorized(); } + const userData = jwtService.verify(token); + + if (!userData) { + throw ApiError.notFound(); + } + const user = await findByUserName(userData.username); return user;