From bff0373746eb2b2f17edd18a914a86d85f93a8f2 Mon Sep 17 00:00:00 2001 From: MaksOther Date: Mon, 2 Mar 2026 13:32:55 +0200 Subject: [PATCH 1/3] added --- .github/workflows/test.yml-template | 23 ++++++++++++ package-lock.json | 35 ++++++++++-------- package.json | 2 +- src/index.js | 57 +++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/test.yml-template diff --git a/.github/workflows/test.yml-template b/.github/workflows/test.yml-template new file mode 100644 index 000000000..bb13dfc45 --- /dev/null +++ b/.github/workflows/test.yml-template @@ -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 diff --git a/package-lock.json b/package-lock.json index ce07e1dca..59f8413ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "GPL-3.0", "devDependencies": { "@mate-academy/eslint-config": "latest", - "@mate-academy/scripts": "^1.8.6", + "@mate-academy/scripts": "^2.1.3", "eslint": "^8.57.0", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-node": "^11.1.0", @@ -59,6 +59,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -1467,10 +1468,11 @@ } }, "node_modules/@mate-academy/scripts": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.8.6.tgz", - "integrity": "sha512-b4om/whj4G9emyi84ORE3FRZzCRwRIesr8tJHXa8EvJdOaAPDpzcJ8A0sFfMsWH9NUOVmOwkBtOXDu5eZZ00Ig==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz", + "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/rest": "^17.11.2", "@types/get-port": "^4.2.0", @@ -1536,7 +1538,6 @@ "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", "dev": true, - "peer": true, "engines": { "node": ">= 18" } @@ -1565,7 +1566,6 @@ "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", "dev": true, - "peer": true, "dependencies": { "@octokit/types": "^13.0.0", "universal-user-agent": "^7.0.2" @@ -1579,7 +1579,6 @@ "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz", "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", "dev": true, - "peer": true, "dependencies": { "@octokit/request": "^9.0.0", "@octokit/types": "^13.0.0", @@ -1593,8 +1592,7 @@ "version": "22.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@octokit/plugin-paginate-rest": { "version": "2.21.3", @@ -1656,7 +1654,6 @@ "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz", "integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==", "dev": true, - "peer": true, "dependencies": { "@octokit/endpoint": "^10.0.0", "@octokit/request-error": "^6.0.1", @@ -1672,7 +1669,6 @@ "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.4.tgz", "integrity": "sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==", "dev": true, - "peer": true, "dependencies": { "@octokit/types": "^13.0.0" }, @@ -1860,7 +1856,6 @@ "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", "dev": true, - "peer": true, "dependencies": { "@octokit/openapi-types": "^22.2.0" } @@ -2203,6 +2198,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2624,8 +2620,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -2668,6 +2663,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001640", "electron-to-chromium": "^1.4.820", @@ -3381,6 +3377,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3436,6 +3433,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -3526,6 +3524,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -3603,6 +3602,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, + "peer": true, "dependencies": { "eslint-plugin-es": "^3.0.0", "eslint-utils": "^2.0.0", @@ -3653,6 +3653,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz", "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -3676,6 +3677,7 @@ "url": "https://feross.org/support" } ], + "peer": true, "peerDependencies": { "eslint": ">=5.0.0" } @@ -5044,6 +5046,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -7376,6 +7379,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -8307,8 +8311,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/universalify": { "version": "2.0.1", diff --git a/package.json b/package.json index 4f64337fe..78cfd0b37 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "license": "GPL-3.0", "devDependencies": { "@mate-academy/eslint-config": "latest", - "@mate-academy/scripts": "^1.8.6", + "@mate-academy/scripts": "^2.1.3", "eslint": "^8.57.0", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-node": "^11.1.0", diff --git a/src/index.js b/src/index.js index ad9a93a7c..c9ad9b08e 100644 --- a/src/index.js +++ b/src/index.js @@ -1 +1,58 @@ 'use strict'; + +import express from 'express'; +import cors from 'cors'; +import { EventEmitter } from 'events'; +import { WebSocketServer } from 'ws'; + +const PORT = process.env.PORT || 3000; +const app = express(); + +app.use(express.json()); +app.use(cors()); + +const emmiter = new EventEmitter(); +const messages = []; + +app.post('/messages', (req, res) => { + const { text } = req.body; + + const message = { + text, + time: new Date(), + }; + + messages.push(message); + emmiter.emit('message', message); + res.status(201).send(messages); +}); + +app.get('/messages', (req, res) => { + emmiter.once('message', (message) => { + res.status(200).send(messages); + }); +}); + +const server = app.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}/`); +}); + +const wss = new WebSocketServer({ server }); + +wss.on('connection', (connection) => { + connection.on('message', (text) => { + const message = { + text: text.toString(), + time: new Date(), + }; + + messages.push(message); + emmiter.emit('message', message); + }); +}); + +emmiter.on('message', (message) => { + for (const client of wss.clients) { + client.send(JSON.stringify(message)); + } +}); From e7713b61b309ae7bd9950cfacf082df008ab235b Mon Sep 17 00:00:00 2001 From: MaksOther Date: Mon, 2 Mar 2026 13:36:11 +0200 Subject: [PATCH 2/3] added --- src/index.js | 86 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 21 deletions(-) diff --git a/src/index.js b/src/index.js index c9ad9b08e..850c68119 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,5 @@ -'use strict'; - import express from 'express'; import cors from 'cors'; -import { EventEmitter } from 'events'; import { WebSocketServer } from 'ws'; const PORT = process.env.PORT || 3000; @@ -11,26 +8,59 @@ const app = express(); app.use(express.json()); app.use(cors()); -const emmiter = new EventEmitter(); -const messages = []; +const rooms = {}; app.post('/messages', (req, res) => { - const { text } = req.body; + const { text, author, room = 'general' } = req.body; + + if (!rooms[room]) rooms[room] = []; const message = { text, + author, // ✅ додано author time: new Date(), + room, }; - messages.push(message); - emmiter.emit('message', message); - res.status(201).send(messages); + rooms[room].push(message); + + for (const client of wss.clients) { + if (client.room === room && client.readyState === 1) { + client.send(JSON.stringify(message)); + } + } + + res.status(201).send(rooms[room]); }); app.get('/messages', (req, res) => { - emmiter.once('message', (message) => { - res.status(200).send(messages); - }); + const { room = 'general' } = req.query; + res.status(200).send(rooms[room] || []); +}); + +app.get('/rooms', (req, res) => { + res.status(200).send(Object.keys(rooms)); +}); + +app.post('/rooms', (req, res) => { + const { name } = req.body; + if (!rooms[name]) rooms[name] = []; + res.status(201).send({ name }); +}); + +app.patch('/rooms/:name', (req, res) => { + const { name } = req.params; + const { newName } = req.body; + if (!rooms[name]) return res.status(404).send({ error: 'Room not found' }); + rooms[newName] = rooms[name]; + delete rooms[name]; + res.status(200).send({ name: newName }); +}); + +app.delete('/rooms/:name', (req, res) => { + const { name } = req.params; + delete rooms[name]; + res.status(200).send({ deleted: name }); }); const server = app.listen(PORT, () => { @@ -40,19 +70,33 @@ const server = app.listen(PORT, () => { const wss = new WebSocketServer({ server }); wss.on('connection', (connection) => { - connection.on('message', (text) => { + connection.on('message', (data) => { + const { text, author, room = 'general' } = JSON.parse(data); + + connection.room = room; + + if (!rooms[room]) rooms[room] = []; + const message = { - text: text.toString(), + text, + author, // ✅ додано author time: new Date(), + room, }; - messages.push(message); - emmiter.emit('message', message); + rooms[room].push(message); + + for (const client of wss.clients) { + if (client.room === room && client.readyState === 1) { + client.send(JSON.stringify(message)); + } + } }); -}); -emmiter.on('message', (message) => { - for (const client of wss.clients) { - client.send(JSON.stringify(message)); - } + connection.on('room_join', (room) => { + connection.room = room; + if (rooms[room]) { + connection.send(JSON.stringify({ history: rooms[room] })); + } + }); }); From 014b3fd8fc016a3f6e56f5e5d65a45f066503dfd Mon Sep 17 00:00:00 2001 From: MaksOther Date: Mon, 2 Mar 2026 13:43:32 +0200 Subject: [PATCH 3/3] edded --- src/index.js | 130 ++++++++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 59 deletions(-) diff --git a/src/index.js b/src/index.js index 850c68119..ae708caa9 100644 --- a/src/index.js +++ b/src/index.js @@ -2,101 +2,113 @@ import express from 'express'; import cors from 'cors'; import { WebSocketServer } from 'ws'; -const PORT = process.env.PORT || 3000; const app = express(); - -app.use(express.json()); app.use(cors()); +app.use(express.json()); -const rooms = {}; - -app.post('/messages', (req, res) => { - const { text, author, room = 'general' } = req.body; +const PORT = process.env.PORT || 3000; +const rooms = { general: [] }; // одразу створюємо general +function ensureRoom(room) { if (!rooms[room]) rooms[room] = []; +} - const message = { - text, - author, // ✅ додано author - time: new Date(), - room, - }; - - rooms[room].push(message); - - for (const client of wss.clients) { +function broadcast(room, data) { + wss.clients.forEach((client) => { if (client.room === room && client.readyState === 1) { - client.send(JSON.stringify(message)); + client.send(JSON.stringify(data)); } - } + }); +} - res.status(201).send(rooms[room]); +app.get('/messages', (req, res) => { + const room = req.query.room || 'general'; + res.send(rooms[room] || []); }); -app.get('/messages', (req, res) => { - const { room = 'general' } = req.query; - res.status(200).send(rooms[room] || []); +app.post('/messages', (req, res) => { + const { text, author, room = 'general' } = req.body; + + ensureRoom(room); + + const message = { text, author, time: new Date(), room }; + rooms[room].push(message); + + broadcast(room, { type: 'message', ...message }); + + res.status(201).send(message); }); app.get('/rooms', (req, res) => { - res.status(200).send(Object.keys(rooms)); + res.send(Object.keys(rooms)); }); app.post('/rooms', (req, res) => { const { name } = req.body; - if (!rooms[name]) rooms[name] = []; + ensureRoom(name); res.status(201).send({ name }); }); -app.patch('/rooms/:name', (req, res) => { - const { name } = req.params; - const { newName } = req.body; - if (!rooms[name]) return res.status(404).send({ error: 'Room not found' }); - rooms[newName] = rooms[name]; - delete rooms[name]; - res.status(200).send({ name: newName }); -}); - app.delete('/rooms/:name', (req, res) => { const { name } = req.params; + + if (!rooms[name]) { + return res.status(404).send({ error: 'Room not found' }); + } + delete rooms[name]; - res.status(200).send({ deleted: name }); + + wss.clients.forEach((client) => { + if (client.room === name) { + client.room = 'general'; + client.send( + JSON.stringify({ + type: 'room_deleted', + redirectTo: 'general', + }), + ); + } + }); + + res.send({ deleted: name }); }); + const server = app.listen(PORT, () => { - console.log(`Server running at http://localhost:${PORT}/`); + console.log(`Server running on http://localhost:${PORT}`); }); const wss = new WebSocketServer({ server }); -wss.on('connection', (connection) => { - connection.on('message', (data) => { - const { text, author, room = 'general' } = JSON.parse(data); +wss.on('connection', (socket) => { + socket.room = 'general'; - connection.room = room; + socket.on('message', (data) => { + const msg = JSON.parse(data); - if (!rooms[room]) rooms[room] = []; + if (msg.type === 'join') { + ensureRoom(msg.room); + socket.room = msg.room; - const message = { - text, - author, // ✅ додано author - time: new Date(), - room, - }; - - rooms[room].push(message); - - for (const client of wss.clients) { - if (client.room === room && client.readyState === 1) { - client.send(JSON.stringify(message)); - } + socket.send( + JSON.stringify({ + type: 'history', + messages: rooms[msg.room], + }), + ); + return; } - }); - connection.on('room_join', (room) => { - connection.room = room; - if (rooms[room]) { - connection.send(JSON.stringify({ history: rooms[room] })); + if (msg.type === 'message') { + const message = { + text: msg.text, + author: msg.author, + time: new Date(), + room: socket.room, + }; + + rooms[socket.room].push(message); + broadcast(socket.room, { type: 'message', ...message }); } }); });