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
40 changes: 40 additions & 0 deletions src/controllers/message.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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, next) => {
try {
const { roomId } = req.params;

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

res.send(allMessages);
} catch (error) {
next(error);
}
};

const createMessage = async (req, res, next) => {
try {
const { text } = req.body;
const { roomId } = req.params;

if (!text) {
throw ApiError.badRequest('Enter the message');
}

const user = await userService.getUser(req);

await messageService.createMessage(user, text, roomId);

res.status(201).send(text);
} catch (error) {
next(error);
}
};

export const messageController = {
getAllMessages,
createMessage,
};
28 changes: 28 additions & 0 deletions src/controllers/refresh.controller.js
Original file line number Diff line number Diff line change
@@ -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,
};
101 changes: 101 additions & 0 deletions src/controllers/room.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
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, next) => {
try {
const rooms = await Room.findAll();

res.send(rooms);
} catch (error) {
next(error);
}
};

const getRoomById = async (req, res, next) => {
try {
const { roomId } = req.params;

const room = await roomService.getRoomById(roomId);

if (!room) {
throw ApiError.notFound();
}

res.send(room);
} catch (error) {
next(error);
}
};

const createRoom = async (req, res, next) => {
try {
const { title, participants } = req.body;

if (!title) {
throw ApiError.badRequest('Enter the title');
}

const user = await userService.getUser(req);

await roomService.createRoom(user, title, participants);

res.status(201).send({ message: 'Room created' });
} catch (error) {
next(error);
}
};

const updateRoom = async (req, res, next) => {
try {
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');
}

const user = await userService.getUser(req);

await roomService.updateRoom(user, room, title, participants);

const updatedRoom = await roomService.getRoomById(roomId);

res.send(updatedRoom);
} catch (error) {
next(error);
}
};

const deleteRoom = async (req, res, next) => {
try {
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' });
} catch (error) {
next(error);
}
};

export const roomController = {
createRoom,
updateRoom,
deleteRoom,
getAllRooms,
getRoomById,
};
43 changes: 43 additions & 0 deletions src/controllers/user.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +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, next) => {
try {
const { username } = req.body;

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);

res.cookie('refreshToken', refreshAccessToken, {
maxAge: 30 * 24 * 60 * 60 * 1000,
httpOnly: true,
});

res.send({ user: normalizedUser, accessToken });
};

export const userController = {
createUser,
generateToken,
};
32 changes: 32 additions & 0 deletions src/exeptions/api.error.js
Original file line number Diff line number Diff line change
@@ -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,
});
}
}
40 changes: 40 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,41 @@
'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';
import cookieParser from 'cookie-parser';
import { refreshRouter } from './routes/refresh.route.js';

const PORT = process.env.PORT || 3005;

const app = express();

app.use(cors());
app.use(express.json());
app.use(cookieParser());

app.use('/user', userRouter);
app.use('/rooms', roomsRouter);
app.use('/messages', messageRouter);

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: The message router is used without a base path (app.use(messageRouter);). This can cause route conflicts and ambiguity. Please specify a base path, e.g., app.use('/messages', messageRouter);.

app.use('/refresh', refreshRouter);

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));
}
});
24 changes: 24 additions & 0 deletions src/middlewares/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { jwtService } from '../services/jwt.service.js';

export const authMiddleware = (req, res, next) => {
const authorization = req.headers['authorization'] || '';
const [, token] = authorization.split(' ');

if (!authorization || !token) {
res.sendStatus(401);

return;
}

const userData = jwtService.verify(token);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Potential issue: After verifying the token, the decoded user data (userData) is not attached to the req object (e.g., req.user = userData). If your controllers or services expect to access the authenticated user's information from the request, you should attach it here. Please check if this is required by your application logic.


if (!userData) {
res.sendStatus(401);

return;
}

req.user = userData;

next();
};
15 changes: 15 additions & 0 deletions src/middlewares/errorMiddleware.js
Original file line number Diff line number Diff line change
@@ -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);
};
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 '../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,
},
},
{
timestamps: false,
},
);

Message.belongsTo(User);
User.hasMany(Message);
Message.belongsTo(Room);
Room.hasMany(Message);
18 changes: 18 additions & 0 deletions src/models/room.js
Original file line number Diff line number Diff line change
@@ -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);
13 changes: 13 additions & 0 deletions src/models/token.js
Original file line number Diff line number Diff line change
@@ -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);
Loading