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
23 changes: 23 additions & 0 deletions .github/workflows/test.yml-template
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Test

on:
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
25 changes: 25 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,28 @@ node_modules

# MacOS
.DS_Store

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
6 changes: 6 additions & 0 deletions back-end/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
PORT=3000
DB_HOST=localhost
DB_USER=postgres
DB_PASSWORD=0011
DB_DATABASE=postgres
DB_PORT=5555
7 changes: 7 additions & 0 deletions back-end/setupDB.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { sequelize } from './src/utils/db.js';
import './src/models/index.js';

sequelize.sync({ alter: true }).then(() => {
// eslint-disable-next-line no-console
console.log('Database synced');
});
43 changes: 43 additions & 0 deletions back-end/src/controllers/auth.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ApiError } from '../exeptions/api.error.js';
import { userService } from '../services/user.service.js';

const login = async (req, res) => {
const { userName } = req.body;

try {
const activeUser = await userService.getUser(userName);

res.cookie('activeUser', activeUser, {
httpOnly: true,
secure: false,
sameSite: 'lax',
maxAge: 30 * 24 * 60 * 60 * 1000,
});
Comment on lines +10 to +15
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: You are storing the activeUser object directly in the cookie. Cookies can only store strings. Please use JSON.stringify(activeUser) when setting the cookie to ensure proper serialization.

Comment on lines +10 to +15
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: You are storing the entire user object directly as a cookie value. Cookies can only store strings, so you should use JSON.stringify(activeUser) when setting the cookie value. This will prevent runtime errors when retrieving and parsing the cookie later.


// localStorage.setItem('activeUser', JSON.stringify(activeUser));

res.send(activeUser);
} catch (e) {
throw ApiError.badRequest(e);
Comment on lines +20 to +21
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 catch block does not capture the error object. Use catch (e) and pass the error message to ApiError.badRequest for better error handling and debugging.

}
Comment on lines +20 to +22
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 catch block does not receive the error object. Update the catch block to catch (err) and use err in the error handling logic for better debugging and error reporting.

};

const getUser = async (req, res) => {
const activeUserCookie = req.cookies.activeUser;
let activeUser;

try {
activeUser = JSON.parse(activeUserCookie);
} catch (err) {
throw ApiError.unauthorized(err);
}

const user = await userService.getUser(activeUser.name);

res.send(user);
};

export const authController = {
login,
getUser,
};
44 changes: 44 additions & 0 deletions back-end/src/controllers/message.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { messageService } from '../services/message.service.js';
import { emitter } from '../events/eventEmitter.js';
import { userService } from '../services/user.service.js';

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

const messages = await messageService.getMessages(roomId);

// emitter.once('message', (message) => {
// res.status(200).send(messages);
// });
res.status(200).send(messages);
};

const sendMessage = async (req, res) => {
const { content, name } = req.body;
const { roomId } = req.params;
const activeUser = req.cookies.activeUser;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Critical issue: req.cookies.activeUser will be a string, not an object. If you need to access properties like 'activeUser.name', you must parse the cookie value (e.g., using JSON.parse) if it was stringified when set. Otherwise, this will not work as intended.

const id = activeUser.id;
Comment on lines +19 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Critical issue: req.cookies.activeUser is always a string. If you are storing a stringified object, you must parse it using JSON.parse(req.cookies.activeUser) before accessing properties like activeUser.id.

Comment on lines +19 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error: You are treating req.cookies.activeUser as an object, but cookies are always strings. You need to parse the cookie value using JSON.parse(req.cookies.activeUser) before accessing its properties (e.g., id). This is a critical issue that will cause runtime errors.

// const { id } = await userService.getUser(activeUser.name);

console.log(activeUser.id);
console.log('strange');

const message = {
content,
userId: id,
roomId,
createdAt: new Date(),
name,
};

await messageService.sendMessage(message);

emitter.emit('message', message);

res.status(201).send();
};

export const messageController = {
getMessages,
sendMessage,
};
75 changes: 75 additions & 0 deletions back-end/src/controllers/room.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ApiError } from '../exeptions/api.error.js';
import { roomService } from '../services/rooms.service.js';
import { userService } from '../services/user.service.js';

const getAll = async (req, res) => {
const rooms = await roomService.getAll();

res.send(rooms);
};

const create = async (req, res) => {
const { roomName } = req.body;

console.log('Room name: ', roomName);

const newRoom = await roomService.create(roomName);

res.send(newRoom);
};

const rename = async (req, res) => {
const { id } = req.params;
const { newRoomName } = req.body;

console.log('roomId: ', id);
console.log('newRoomName: ', newRoomName);

const updatedRoom = await roomService.rename(id, newRoomName);

res.send(updatedRoom);
};

const join = async (req, res) => {
const { roomId } = req.params;
const activeUser = req.cookies.activeUser;
const user = await userService.getUser(activeUser.name);
Comment on lines +35 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Critical issue: req.cookies.activeUser will be a string, not an object. Attempting to access 'activeUser.name' will result in 'undefined'. You should parse the cookie value (e.g., using JSON.parse) if you are storing an object as a cookie, or store only the username as the cookie value.

Comment on lines +35 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Critical issue: req.cookies.activeUser is always a string. If you are storing a stringified object, you must parse it using JSON.parse(req.cookies.activeUser) before accessing properties like activeUser.name.

Comment on lines +35 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error: You are treating req.cookies.activeUser as an object, but cookies are always strings. You need to parse the cookie value using JSON.parse(req.cookies.activeUser) before accessing its properties (e.g., name). This is a critical issue that will cause runtime errors.


try {
await roomService.join(roomId, user);
res
.status(200)
.json({ message: `User joined room ${roomId} successfully.` });
} catch (error) {
console.error('Error joining room:', error);
}
Comment on lines +43 to +45
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: In the catch block, you are only logging the error and not sending any response to the client. This will cause the request to hang if an error occurs. Please send an appropriate error response (e.g., res.status(500).json({ error: 'Failed to join room' })).

};

const remove = async (req, res) => {
const { id } = req.params;

await roomService.remove(id);

res.status(201).send();
};

const getJoined = async (req, res) => {
const { id } = req.params;

// if (id instanceof undefined) {
// throw ApiError.badRequest('Id is undefined');
// }

const roomIds = (await roomService.getJoined(id)) || [];

res.send(roomIds);
};

export const roomController = {
getAll,
create,
rename,
join,
remove,
getJoined,
};
3 changes: 3 additions & 0 deletions back-end/src/events/eventEmitter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { EventEmitter } from 'events';

export const emitter = new EventEmitter();
8 changes: 8 additions & 0 deletions back-end/src/events/messageListener.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { emitter } from './eventEmitter.js';
import { wss } from '../index.js';

emitter.on('message', (message) => {
for (const client of wss.clients) {
client.send(JSON.stringify(message));
}
});
32 changes: 32 additions & 0 deletions back-end/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,
});
}
}
39 changes: 39 additions & 0 deletions back-end/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import express from 'express';
import cookieParser from 'cookie-parser';
import cors from 'cors';
import { authRouter } from './routes/auth.route.js';
import { errorMiddleware } from './middlewares/errorMiddleware.js';
import { roomRouter } from './routes/room.route.js';
import { WebSocketServer } from 'ws';
import { messageRouter } from './routes/message.route.js';
import './events/messageListener.js';

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

const app = express();

app.use(
cors({
origin: 'http://localhost:5173',
credentials: true,
}),
);
app.use(express.json());
app.use(cookieParser());

app.use(authRouter);
app.use(roomRouter);
app.use(messageRouter);

app.get('/', (req, res) => {
res.send('Hello');
});

app.use(errorMiddleware);

const server = app.listen(PORT, () => {
// eslint-disable-next-line no-console
console.log('Server is running...');
});

export const wss = new WebSocketServer({ server });
15 changes: 15 additions & 0 deletions back-end/src/middlewares/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const authMiddleware = (req, res, next) => {
const activeUser = req.cookies.activeUser;
// const activeUser = localStorage.getItem('activeUser');

console.log('Active user: ', activeUser);

if (activeUser === undefined) {
console.log('error');

res.status(401).json({ message: 'Unauthorized' });
return;
}

next();
};
16 changes: 16 additions & 0 deletions back-end/src/middlewares/errorMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ApiError } from '../exeptions/api.error.js';

export const errorMiddleware = (error, req, res, next) => {
if (error instanceof ApiError) {
return res.status(error.status).send({
message: error.message,
errors: error.errors,
});
}

if (error) {
return res.status(500).send({
message: 'Server error',
});
}
};
14 changes: 14 additions & 0 deletions back-end/src/models/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { User } from './user.js';
import { Room } from './room.js';
import { Message } from './message.js';

User.hasMany(Message);
Message.belongsTo(User);

Room.hasMany(Message);
Message.belongsTo(Room);

User.belongsToMany(Room, { through: 'UserRooms', as: 'rooms' });
Room.belongsToMany(User, { through: 'UserRooms', as: 'users' });

export { User, Room, Message };
9 changes: 9 additions & 0 deletions back-end/src/models/message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { DataTypes } from 'sequelize';
import { sequelize } from '../utils/db.js';

export const Message = sequelize.define('Message', {
content: {
type: DataTypes.TEXT,
allowNull: false,
},
});
9 changes: 9 additions & 0 deletions back-end/src/models/room.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { DataTypes } from 'sequelize';
import { sequelize } from '../utils/db.js';

export const Room = sequelize.define('Room', {
roomName: {
type: DataTypes.STRING,
allowNull: false,
},
});
10 changes: 10 additions & 0 deletions back-end/src/models/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DataTypes } from 'sequelize';
import { sequelize } from '../utils/db.js';

export const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
});
Loading
Loading