From e5a69fa4a9d817d8662f55d61f340ac2f1edb59d Mon Sep 17 00:00:00 2001
From: Wenszel
Date: Wed, 20 Dec 2023 10:17:49 +0100
Subject: [PATCH 1/4] fixed timeout issue
implemented timeoutManager, a more efficient solution for managing timeouts in RAM memory. this resolves the problem of lingering timeouts in the server, providing faster removal of old timeouts and improving overall performance.
---
README.md | 29 ++-
backend/handlers/playerHandler.js | 4 +-
backend/models/room.js | 26 +-
backend/models/timeoutManager.js | 19 ++
backend/package-lock.json | 275 +++++++++++++++++++-
backend/package.json | 1 +
backend/tests/models/timeoutManager.test.js | 50 ++++
backend/utils/constants.js | 5 +-
8 files changed, 381 insertions(+), 28 deletions(-)
create mode 100644 backend/models/timeoutManager.js
create mode 100644 backend/tests/models/timeoutManager.test.js
diff --git a/README.md b/README.md
index eeaa3ea..af4d172 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,14 @@
-# Online Multiplayer Ludo Game
-
-## About
+Online Multiplayer Ludo Game
Ludo Online is a multiplayer web-based implementation of the classic board game Ludo, built using the MERN stack and integrated with SocketIO for real-time communication.
-\>\> Play Online here <<
-\>\> [Watch YouTube Video here](https://youtu.be/mGMnH9Nvsyw) <<
+
+>> Play Online here <<
+
+
+
+>> Watch YouTube Video here <<
+
## Architecture
@@ -14,34 +17,50 @@ Ludo Online is a multiplayer web-based implementation of the classic board game
## Tech Stack
Frontend:
+
  
  
+
Backend:
+
   
+
Tests:
+
  
+
Tools:
+
    
## Key Features and Challenges
- Maintained session consistency with **Express Session** and **MongoDB**.
+
- Enabled real-time communication via **WebSocket** and **SocketIO**.
+
- Maintained code reliability by implementing unit and integration tests using **Mocha**, **Chai**, and **Jest**.
+
- Hosted in a **Docker** container on **AWS EC2**.
+
- Established CI/CD using **CircleCI**.
+
- Implemented E2E tests utilizing **Cypress**, addressing challenges related to [testing collaboration](https://docs.cypress.io/guides/references/trade-offs#Multiple-browsers-open-at-the-same-time) and canvas functionality in the application.
## Installation
1. Download this repository
+
2. Generate your own [mongoDB atlas](https://www.mongodb.com) credential URL. It should looks like this:
```
+
mongodb+srv://madmin:@clustername.mongodb.net/?retryWrites=true&w=majority
+
```
3. Add this URL to the /backend/credentials.js file
+
4. Perform these commands in the main directory:
```
diff --git a/backend/handlers/playerHandler.js b/backend/handlers/playerHandler.js
index 93869e1..4280673 100644
--- a/backend/handlers/playerHandler.js
+++ b/backend/handlers/playerHandler.js
@@ -1,5 +1,5 @@
const { getRoom, updateRoom } = require('../services/roomService');
-const { colors } = require('../utils/constants');
+const { COLORS } = require('../utils/constants');
module.exports = socket => {
const req = socket.request;
@@ -44,7 +44,7 @@ module.exports = socket => {
if (err) return socket.disconnect();
req.session.roomId = room._id.toString();
req.session.playerId = room.players[room.players.length - 1]._id.toString();
- req.session.color = colors[room.players.length - 1];
+ req.session.color = COLORS[room.players.length - 1];
req.session.save();
socket.join(room._id.toString());
socket.emit('player:data', JSON.stringify(req.session));
diff --git a/backend/models/room.js b/backend/models/room.js
index 47fd2f7..b427494 100644
--- a/backend/models/room.js
+++ b/backend/models/room.js
@@ -1,6 +1,7 @@
const mongoose = require('mongoose');
-const { colors } = require('../utils/constants');
+const { COLORS, MOVE_TIME } = require('../utils/constants');
const { makeRandomMove } = require('../handlers/handlersFunctions');
+const timeoutManager = require('./timeoutManager.js');
const PawnSchema = require('./pawn');
const PlayerSchema = require('./player');
@@ -12,7 +13,6 @@ const RoomSchema = new mongoose.Schema({
started: { type: Boolean, default: false },
full: { type: Boolean, default: false },
nextMoveTime: Number,
- timeoutID: Number,
rolledNumber: Number,
players: [PlayerSchema],
winner: { type: String, default: null },
@@ -24,10 +24,10 @@ const RoomSchema = new mongoose.Schema({
let pawn = {};
pawn.basePos = i;
pawn.position = i;
- if (i < 4) pawn.color = colors[0];
- else if (i < 8) pawn.color = colors[1];
- else if (i < 12) pawn.color = colors[2];
- else if (i < 16) pawn.color = colors[3];
+ if (i < 4) pawn.color = COLORS[0];
+ else if (i < 8) pawn.color = COLORS[1];
+ else if (i < 12) pawn.color = COLORS[2];
+ else if (i < 16) pawn.color = COLORS[3];
startPositions.push(pawn);
}
return startPositions;
@@ -54,10 +54,10 @@ RoomSchema.methods.changeMovingPlayer = function () {
} else {
this.players[playerIndex + 1].nowMoving = true;
}
- this.nextMoveTime = Date.now() + 15000;
+ this.nextMoveTime = Date.now() + MOVE_TIME;
this.rolledNumber = null;
- if (this.timeoutID) clearTimeout(this.timeoutID);
- this.timeoutID = setTimeout(makeRandomMove, 15000, this._id.toString());
+ timeoutManager.clear(this._id.toString());
+ timeoutManager.set(makeRandomMove, MOVE_TIME, this._id.toString());
};
RoomSchema.methods.movePawn = function (pawn) {
@@ -83,14 +83,14 @@ RoomSchema.methods.canStartGame = function () {
RoomSchema.methods.startGame = function () {
this.started = true;
- this.nextMoveTime = Date.now() + 15000;
+ this.nextMoveTime = Date.now() + MOVE_TIME;
this.players.forEach(player => (player.ready = true));
this.players[0].nowMoving = true;
- this.timeoutID = setTimeout(makeRandomMove, 15000, this._id.toString());
+ timeoutManager.set(makeRandomMove, MOVE_TIME, this._id.toString());
};
RoomSchema.methods.endGame = function (winner) {
- this.timeoutID = null;
+ timeoutManager.clear(this._id.toString());
this.rolledNumber = null;
this.nextMoveTime = null;
this.players.map(player => (player.nowMoving = false));
@@ -131,7 +131,7 @@ RoomSchema.methods.addPlayer = function (name, id) {
sessionID: id,
name: name,
ready: false,
- color: colors[this.players.length],
+ color: COLORS[this.players.length],
});
};
diff --git a/backend/models/timeoutManager.js b/backend/models/timeoutManager.js
new file mode 100644
index 0000000..a7f04d9
--- /dev/null
+++ b/backend/models/timeoutManager.js
@@ -0,0 +1,19 @@
+const timeoutManager = {
+ timeouts: new Map(),
+ add: function (roomId, timeoutId) {
+ this.timeouts.set(roomId, timeoutId);
+ },
+ get: function (roomId) {
+ return this.timeouts.get(roomId);
+ },
+ clear: function (roomId) {
+ clearTimeout(this.timeouts.get(roomId));
+ this.timeouts.delete(roomId);
+ },
+ set: function (timeoutFunction, time, roomId) {
+ const timeoutId = setTimeout(timeoutFunction, time, roomId);
+ this.add(roomId, timeoutId);
+ },
+};
+
+module.exports = timeoutManager;
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 122fc4f..df591ca 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -13,6 +13,7 @@
"express": "^4.17.1",
"express-session": "^1.17.1",
"mongoose": "^5.12.0",
+ "sinon": "^17.0.1",
"socket.io": "^4.5.1"
},
"devDependencies": {
@@ -21,6 +22,45 @@
"socket.io-client": "^4.7.2"
}
},
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz",
+ "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "11.2.2",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz",
+ "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
+ "node_modules/@sinonjs/samsam": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz",
+ "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==",
+ "dependencies": {
+ "@sinonjs/commons": "^2.0.0",
+ "lodash.get": "^4.4.2",
+ "type-detect": "^4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz",
+ "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/text-encoding": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz",
+ "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ=="
+ },
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
@@ -1081,7 +1121,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -1257,6 +1296,11 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/just-extend": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz",
+ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg=="
+ },
"node_modules/kareem": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz",
@@ -1293,6 +1337,11 @@
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
},
+ "node_modules/lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
+ },
"node_modules/lodash.set": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
@@ -1676,6 +1725,55 @@
"node": ">= 0.6"
}
},
+ "node_modules/nise": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz",
+ "integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==",
+ "dependencies": {
+ "@sinonjs/commons": "^2.0.0",
+ "@sinonjs/fake-timers": "^10.0.2",
+ "@sinonjs/text-encoding": "^0.7.1",
+ "just-extend": "^4.0.2",
+ "path-to-regexp": "^1.7.0"
+ }
+ },
+ "node_modules/nise/node_modules/@sinonjs/commons": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz",
+ "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/nise/node_modules/@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
+ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
+ "node_modules/nise/node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz",
+ "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/nise/node_modules/isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
+ },
+ "node_modules/nise/node_modules/path-to-regexp": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
+ "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+ "dependencies": {
+ "isarray": "0.0.1"
+ }
+ },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -2039,6 +2137,42 @@
"resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz",
"integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA=="
},
+ "node_modules/sinon": {
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz",
+ "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0",
+ "@sinonjs/fake-timers": "^11.2.2",
+ "@sinonjs/samsam": "^8.0.0",
+ "diff": "^5.1.0",
+ "nise": "^5.1.5",
+ "supports-color": "^7.2.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/sinon"
+ }
+ },
+ "node_modules/sinon/node_modules/diff": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
+ "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/sinon/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/sliced": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
@@ -2306,7 +2440,6 @@
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
- "dev": true,
"engines": {
"node": ">=4"
}
@@ -2506,6 +2639,47 @@
}
},
"dependencies": {
+ "@sinonjs/commons": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz",
+ "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==",
+ "requires": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "@sinonjs/fake-timers": {
+ "version": "11.2.2",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz",
+ "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==",
+ "requires": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
+ "@sinonjs/samsam": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz",
+ "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==",
+ "requires": {
+ "@sinonjs/commons": "^2.0.0",
+ "lodash.get": "^4.4.2",
+ "type-detect": "^4.0.8"
+ },
+ "dependencies": {
+ "@sinonjs/commons": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz",
+ "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==",
+ "requires": {
+ "type-detect": "4.0.8"
+ }
+ }
+ }
+ },
+ "@sinonjs/text-encoding": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz",
+ "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ=="
+ },
"@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
@@ -3291,8 +3465,7 @@
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"he": {
"version": "1.2.0",
@@ -3412,6 +3585,11 @@
"argparse": "^2.0.1"
}
},
+ "just-extend": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz",
+ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg=="
+ },
"kareem": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz",
@@ -3439,6 +3617,11 @@
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
},
+ "lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
+ },
"lodash.set": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
@@ -3711,6 +3894,59 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
+ "nise": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz",
+ "integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==",
+ "requires": {
+ "@sinonjs/commons": "^2.0.0",
+ "@sinonjs/fake-timers": "^10.0.2",
+ "@sinonjs/text-encoding": "^0.7.1",
+ "just-extend": "^4.0.2",
+ "path-to-regexp": "^1.7.0"
+ },
+ "dependencies": {
+ "@sinonjs/commons": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz",
+ "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==",
+ "requires": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
+ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "requires": {
+ "@sinonjs/commons": "^3.0.0"
+ },
+ "dependencies": {
+ "@sinonjs/commons": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz",
+ "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==",
+ "requires": {
+ "type-detect": "4.0.8"
+ }
+ }
+ }
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
+ },
+ "path-to-regexp": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
+ "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+ "requires": {
+ "isarray": "0.0.1"
+ }
+ }
+ }
+ },
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -3989,6 +4225,34 @@
"resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz",
"integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA=="
},
+ "sinon": {
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz",
+ "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==",
+ "requires": {
+ "@sinonjs/commons": "^3.0.0",
+ "@sinonjs/fake-timers": "^11.2.2",
+ "@sinonjs/samsam": "^8.0.0",
+ "diff": "^5.1.0",
+ "nise": "^5.1.5",
+ "supports-color": "^7.2.0"
+ },
+ "dependencies": {
+ "diff": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
+ "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw=="
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"sliced": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
@@ -4187,8 +4451,7 @@
"type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
- "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
- "dev": true
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="
},
"type-is": {
"version": "1.6.18",
diff --git a/backend/package.json b/backend/package.json
index 751caf9..c45bf84 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -8,6 +8,7 @@
"express": "^4.17.1",
"express-session": "^1.17.1",
"mongoose": "^5.12.0",
+ "sinon": "^17.0.1",
"socket.io": "^4.5.1"
},
"devDependencies": {
diff --git a/backend/tests/models/timeoutManager.test.js b/backend/tests/models/timeoutManager.test.js
new file mode 100644
index 0000000..692ef5e
--- /dev/null
+++ b/backend/tests/models/timeoutManager.test.js
@@ -0,0 +1,50 @@
+const chai = require('chai');
+const sinon = require('sinon');
+const timeoutManager = require('../../models/timeoutManager');
+
+const { expect } = chai;
+
+describe('timeoutManager', () => {
+ beforeEach(() => {
+ timeoutManager.timeouts.clear();
+ sinon.useFakeTimers({ shouldClearNativeTimers: true });
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it('should add a timeout to the map', () => {
+ timeoutManager.add('room1', 1);
+ expect(timeoutManager.timeouts.size).to.equal(1);
+ });
+
+ it('should get a timeout from the map', () => {
+ timeoutManager.add('room1', 1);
+ const timeoutId = timeoutManager.get('room1');
+ expect(timeoutId).to.equal(1);
+ });
+
+ it('should clear a timeout from the map', () => {
+ timeoutManager.add('room1', 1);
+ timeoutManager.clear('room1');
+ expect(timeoutManager.timeouts.size).to.equal(0);
+ });
+
+ it('should set a new timeout', () => {
+ const timeoutFunction = sinon.spy();
+ timeoutManager.set(timeoutFunction, 100, 'room1');
+ expect(timeoutManager.timeouts.size).to.equal(1);
+ sinon.clock.tick(101);
+ sinon.assert.calledOnce(timeoutFunction);
+ });
+
+ it('should not call the timeout function if cleared', () => {
+ const timeoutFunction = sinon.spy();
+ timeoutManager.set(timeoutFunction, 100, 'room1');
+ timeoutManager.clear('room1');
+ sinon.clock.tick(101);
+ sinon.assert.notCalled(timeoutFunction);
+ expect(timeoutManager.timeouts.size).to.equal(0);
+ });
+});
diff --git a/backend/utils/constants.js b/backend/utils/constants.js
index a65048d..15978cb 100644
--- a/backend/utils/constants.js
+++ b/backend/utils/constants.js
@@ -1,2 +1,3 @@
-const colors = ["red", "blue", "green", "yellow"];
-module.exports = { colors };
+const COLORS = ['red', 'blue', 'green', 'yellow'];
+const MOVE_TIME = 15000;
+module.exports = { COLORS, MOVE_TIME };
From 766614f5d2e8be28e343368e6893225999d295c2 Mon Sep 17 00:00:00 2001
From: Wenszel
Date: Wed, 20 Dec 2023 18:21:45 +0100
Subject: [PATCH 2/4] added deploying a docker image to docker hub
---
.circleci/config.yml | 23 +++++++++++++++++++++++
.dockerignore | 35 +++++++++++++++++++++++++++++++++++
.gitignore | 3 ++-
Dockerfile | 19 +++++++++++++++++++
README.md | 2 +-
backend/.env.example | 3 +++
backend/config/database.js | 4 +---
backend/config/session.js | 4 ++--
backend/credentials.js | 2 --
backend/package-lock.json | 17 +++++++++++++++++
backend/package.json | 1 +
backend/server.js | 11 +++++++----
package.json | 7 ++-----
13 files changed, 113 insertions(+), 18 deletions(-)
create mode 100644 .dockerignore
create mode 100644 Dockerfile
create mode 100644 backend/.env.example
delete mode 100644 backend/credentials.js
diff --git a/.circleci/config.yml b/.circleci/config.yml
index ac9ca05..1c85a93 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -26,9 +26,32 @@ jobs:
name: Test Frontend
command: |
npm test
+ build_docker_image:
+ docker:
+ - image: circleci/node:14
+
+ working_directory: ~/app
+
+ steps:
+ - checkout
+ - run:
+ name: Build Docker Image
+ command: |
+ docker build -t $DOCKER_HUB_USERNAME/mern-ludo:latest .
+ - run:
+ name: Push Docker Image
+ command: |
+ echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_USERNAME" --password-stdin
+ docker push $DOCKER_HUB_USERNAME/mern-ludo:latest
+
+
workflows:
version: 2
build:
jobs:
- build_and_test
+ - build_docker_image:
+ filters:
+ branches:
+ only: main
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..71a7578
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,35 @@
+# Include any files or directories that you don't want to be copied to your
+# container here (e.g., local build artifacts, temporary files, etc.).
+#
+# For more help, visit the .dockerignore file reference guide at
+# https://docs.docker.com/engine/reference/builder/#dockerignore-file
+
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/.next
+**/.cache
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/charts
+**/docker-compose*
+**/compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+**/build
+**/dist
+LICENSE
+README.md
+node_modules
diff --git a/.gitignore b/.gitignore
index d2d2990..2e01d74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,7 @@ backend/node_modules
/coverage
# production
-/build
+build
# misc
.DS_Store
@@ -21,3 +21,4 @@ backend/node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+.env
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..8c665bc
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+FROM node:14 as frontend
+WORKDIR /app
+COPY . /app
+RUN npm install --production
+RUN npm run build
+
+FROM node:14 as backend
+WORKDIR /app
+COPY /backend /app
+RUN npm install
+
+FROM node:14
+WORKDIR /app
+COPY --from=backend /app /app/
+COPY --from=frontend /app/build /app/build
+
+EXPOSE 8080
+
+CMD ["npm", "run", "start"]
diff --git a/README.md b/README.md
index af4d172..e4ce3f8 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ mongodb+srv://madmin:@clustername.mongodb.net/?retryWrites=tru
```
-3. Add this URL to the /backend/credentials.js file
+3. Add this URL to the /backend/.env file (refer to .env.example)
4. Perform these commands in the main directory:
diff --git a/backend/.env.example b/backend/.env.example
new file mode 100644
index 0000000..52a81f1
--- /dev/null
+++ b/backend/.env.example
@@ -0,0 +1,3 @@
+PORT=8080
+CONNECTION_URI=your_mongodb_connection_uri
+NODE_ENV="development"
\ No newline at end of file
diff --git a/backend/config/database.js b/backend/config/database.js
index 5873253..f347966 100644
--- a/backend/config/database.js
+++ b/backend/config/database.js
@@ -1,9 +1,7 @@
-const CONNECTION_URI = require('../credentials.js');
-
module.exports = function (mongoose) {
mongoose.set('useFindAndModify', false);
mongoose
- .connect(CONNECTION_URI, {
+ .connect(process.env.CONNECTION_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
dbName: 'test',
diff --git a/backend/config/session.js b/backend/config/session.js
index 56ead1c..c66fb40 100644
--- a/backend/config/session.js
+++ b/backend/config/session.js
@@ -1,8 +1,8 @@
const session = require('express-session');
-const CONNECTION_URI = require('../credentials.js');
const MongoDBStore = require('connect-mongodb-session')(session);
+
const store = new MongoDBStore({
- uri: CONNECTION_URI,
+ uri: process.env.CONNECTION_URI,
collection: 'sessions',
});
const sessionMiddleware = session({
diff --git a/backend/credentials.js b/backend/credentials.js
deleted file mode 100644
index 4be1469..0000000
--- a/backend/credentials.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// Write your own mongoDBatlas credentials here
-module.exports = '';
diff --git a/backend/package-lock.json b/backend/package-lock.json
index df591ca..4e797bb 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -10,6 +10,7 @@
"connect-mongodb-session": "^3.1.1",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
+ "dotenv": "^16.3.1",
"express": "^4.17.1",
"express-session": "^1.17.1",
"mongoose": "^5.12.0",
@@ -708,6 +709,17 @@
"node": ">=0.3.1"
}
},
+ "node_modules/dotenv": {
+ "version": "16.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+ "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/motdotla/dotenv?sponsor=1"
+ }
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -3157,6 +3169,11 @@
"integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
"dev": true
},
+ "dotenv": {
+ "version": "16.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+ "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ=="
+ },
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
diff --git a/backend/package.json b/backend/package.json
index c45bf84..3f5149b 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -5,6 +5,7 @@
"connect-mongodb-session": "^3.1.1",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
+ "dotenv": "^16.3.1",
"express": "^4.17.1",
"express-session": "^1.17.1",
"mongoose": "^5.12.0",
diff --git a/backend/server.js b/backend/server.js
index f018038..b4a1cd0 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -1,10 +1,12 @@
const express = require('express');
const cors = require('cors');
+const path = require('path');
const cookieParser = require('cookie-parser');
const mongoose = require('mongoose');
+require('dotenv').config();
const { sessionMiddleware } = require('./config/session');
-const PORT = 8080;
+const PORT = process.env.PORT;
const app = express();
@@ -30,9 +32,10 @@ require('./config/database')(mongoose);
require('./config/socket')(server);
if (process.env.NODE_ENV === 'production') {
- app.use(express.static('/app/build'));
- app.get('/', (req, res) => {
- res.sendFile('/app/build/index.html');
+ app.use(express.static('./build'));
+ app.get('*', (req, res) => {
+ const indexPath = path.join(__dirname, './build/index.html');
+ res.sendFile(indexPath);
});
}
diff --git a/package.json b/package.json
index 5f62271..e7ffcc6 100644
--- a/package.json
+++ b/package.json
@@ -23,9 +23,7 @@
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
- "heroku-postbuild": "cd backend && npm install && cd .. && npm install && npm run build",
- "test": "react-scripts test",
- "eject": "react-scripts eject"
+ "test": "react-scripts test"
},
"eslintConfig": {
"extends": [
@@ -48,11 +46,10 @@
"last 1 safari version"
]
},
- "proxy": "http://localhost:5000",
"devDependencies": {
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"cypress": "^13.6.1",
- "@babel/plugin-proposal-private-property-in-object": "^7.16.7"
+ "@babel/plugin-transform-private-property-in-object": "^7.16.7"
}
}
From 0b21489989866fd31138e60d7b982b1a85706cd1 Mon Sep 17 00:00:00 2001
From: Wenszel
Date: Sun, 24 Dec 2023 19:53:38 +0100
Subject: [PATCH 3/4] added pushing to AWS ECR
---
.circleci/config.yml | 48 ++++++++++++++++++--------------------------
src/App.js | 2 +-
2 files changed, 21 insertions(+), 29 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 1c85a93..048e099 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,12 +1,13 @@
version: 2.1
+orbs:
+ aws-cli: circleci/aws-cli@4.1.2
+ aws-ecr: circleci/aws-ecr@9.0.1
jobs:
build_and_test:
docker:
- image: circleci/node:14
-
working_directory: ~/app
-
steps:
- checkout
- run:
@@ -26,32 +27,23 @@ jobs:
name: Test Frontend
command: |
npm test
- build_docker_image:
- docker:
- - image: circleci/node:14
-
- working_directory: ~/app
-
- steps:
- - checkout
- - run:
- name: Build Docker Image
- command: |
- docker build -t $DOCKER_HUB_USERNAME/mern-ludo:latest .
- - run:
- name: Push Docker Image
- command: |
- echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_USERNAME" --password-stdin
- docker push $DOCKER_HUB_USERNAME/mern-ludo:latest
-
workflows:
- version: 2
- build:
- jobs:
- - build_and_test
- - build_docker_image:
- filters:
- branches:
- only: main
\ No newline at end of file
+ test:
+ jobs:
+ - build_and_test
+ build_and_deploy:
+ jobs:
+ - aws-ecr/build_and_push_image:
+ auth:
+ - aws-cli/setup:
+ role_arn: arn:aws:iam::797929460436:role/openid
+ role_session_name: example-session
+ repo: mern-ludo
+ public_registry: true
+ tag: latest
+ filters:
+ branches:
+ only:
+ - main
\ No newline at end of file
diff --git a/src/App.js b/src/App.js
index 920df19..61034eb 100644
--- a/src/App.js
+++ b/src/App.js
@@ -13,7 +13,7 @@ function App() {
const [playerSocket, setPlayerSocket] = useState();
const [redirect, setRedirect] = useState();
useEffect(() => {
- const socket = io('http://localhost:8080', { withCredentials: true });
+ const socket = io(`http://${window.location.hostname}:8080`, { withCredentials: true });
socket.on('player:data', data => {
data = JSON.parse(data);
setPlayerData(data);
From 4366b6aba48e983ef60d0ddb21b2ef3d281b406c Mon Sep 17 00:00:00 2001
From: Wenszel
Date: Thu, 28 Dec 2023 17:28:27 +0100
Subject: [PATCH 4/4] added deploying to ECS
---
.circleci/config.yml | 60 ++++++++++++-------
README.md | 13 ++--
.../JoinServer/JoinServer.module.css | 2 +-
.../ServersTable/ServersTable.module.css | 3 +
4 files changed, 49 insertions(+), 29 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 048e099..83bfe3b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,8 +1,8 @@
version: 2.1
orbs:
- aws-cli: circleci/aws-cli@4.1.2
- aws-ecr: circleci/aws-ecr@9.0.1
-
+ aws-cli: circleci/aws-cli@4.1.2
+ aws-ecr: circleci/aws-ecr@9.0.1
+ aws-ecs: circleci/aws-ecs@4.0.0
jobs:
build_and_test:
docker:
@@ -27,23 +27,41 @@ jobs:
name: Test Frontend
command: |
npm test
-
+ run_task:
+ docker:
+ - image: cimg/python:3.10
+ steps:
+ - aws-cli/setup:
+ role_arn: arn:aws:iam::797929460436:role/ecs
+ role_session_name: example-session
+ - aws-ecs/run_task:
+ awsvpc: false
+ cluster: arn:aws:ecs:eu-north-1:797929460436:cluster/mern-ludo-cluster
+ launch_type: EC2
+ task_definition: mern-ludo-docker-task
workflows:
- test:
- jobs:
- - build_and_test
- build_and_deploy:
- jobs:
- - aws-ecr/build_and_push_image:
- auth:
- - aws-cli/setup:
- role_arn: arn:aws:iam::797929460436:role/openid
- role_session_name: example-session
- repo: mern-ludo
- public_registry: true
- tag: latest
- filters:
- branches:
- only:
- - main
\ No newline at end of file
+ build_and_test_and_deploy:
+ jobs:
+ - build_and_test
+ - aws-ecr/build_and_push_image:
+ auth:
+ - aws-cli/setup:
+ role_arn: arn:aws:iam::797929460436:role/openid
+ role_session_name: example-session
+ repo: mern-ludo
+ public_registry: true
+ tag: latest
+ requires:
+ - build_and_test
+ filters:
+ branches:
+ only:
+ - main
+ - run_task:
+ requires:
+ - aws-ecr/build_and_push_image
+ filters:
+ branches:
+ only:
+ - main
\ No newline at end of file
diff --git a/README.md b/README.md
index e4ce3f8..71272d7 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@
Ludo Online is a multiplayer web-based implementation of the classic board game Ludo, built using the MERN stack and integrated with SocketIO for real-time communication.
->> Play Online here <<
-
+>> Play online here <<
+
>> Watch YouTube Video here <<
@@ -29,7 +29,7 @@ Tests:
  
-Tools:
+Other:
    
@@ -41,12 +41,11 @@ Tools:
- Maintained code reliability by implementing unit and integration tests using **Mocha**, **Chai**, and **Jest**.
-- Hosted in a **Docker** container on **AWS EC2**.
-
-- Established CI/CD using **CircleCI**.
-
- Implemented E2E tests utilizing **Cypress**, addressing challenges related to [testing collaboration](https://docs.cypress.io/guides/references/trade-offs#Multiple-browsers-open-at-the-same-time) and canvas functionality in the application.
+- Established a CI/CD pipeline using **CircleCI**, with pushing **Docker** container to **AWS ECR** and deploying to **AWS ECS**
+
+
## Installation
1. Download this repository
diff --git a/src/components/LoginPage/JoinServer/JoinServer.module.css b/src/components/LoginPage/JoinServer/JoinServer.module.css
index bd89d03..c2361bb 100644
--- a/src/components/LoginPage/JoinServer/JoinServer.module.css
+++ b/src/components/LoginPage/JoinServer/JoinServer.module.css
@@ -1,7 +1,7 @@
.serversTableContainer {
display: flex;
- height: 500px;
overflow: scroll;
+ height: 500px;
width: 100%;
}
.refresh {
diff --git a/src/components/LoginPage/JoinServer/ServersTable/ServersTable.module.css b/src/components/LoginPage/JoinServer/ServersTable/ServersTable.module.css
index 25636ad..a176bf8 100644
--- a/src/components/LoginPage/JoinServer/ServersTable/ServersTable.module.css
+++ b/src/components/LoginPage/JoinServer/ServersTable/ServersTable.module.css
@@ -1,6 +1,8 @@
.roomName {
max-width: 150px;
overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
text-align: left !important;
}
.rooms > thead > tr :nth-child(2) {
@@ -30,6 +32,7 @@
.rooms {
border-collapse: collapse;
width: 100%;
+ height: fit-content;
}
.lastColumn {