diff --git a/back/api/channels.js b/back/api/channels.js index 9055c15..a504296 100644 --- a/back/api/channels.js +++ b/back/api/channels.js @@ -11,6 +11,16 @@ router.get('/', async (req, res) => { res.send(channels); }); +const sockets = new Set(); + +router.ws('/', function(ws, req) { + sockets.add(ws); + + ws.on('close', () => { + sockets.delete(ws); + }); +}); + router.get('/:name', async (req, res) => { const name = req.params.name; const connection = await getConnection(); @@ -23,6 +33,22 @@ router.get('/:name', async (req, res) => { } }); +const channelSockets = new Map(); + +router.ws('/:name', function(ws, req) { + const name = req.params.name; + + if (!channelSockets.has(name)) { + channelSockets.set(name, new Set()); + } + + channelSockets.get(name).add(ws); + + ws.on('close', () => { + channelSockets.get(name).delete(ws); + }); +}); + router.get('/:name/messages', async (req, res) => { const name = req.params.name; const connection = await getConnection(); @@ -78,6 +104,23 @@ router.post('/:name/messages/send', async (req, res) => { } connection.end(); + + if (sockets.size > 0) { + for (const client of sockets) { + if (client.readyState === 1) { + client.send('new_message'); + } + } + } + + if (channelSockets.has(name)) { + for (const client of channelSockets.get(name)) { + if (client.readyState === 1) { + client.send('new_message'); + } + } + } + res.send({ message: 'Message sent' }); }); @@ -113,6 +156,23 @@ router.post('/:name/messages/delete', async (req, res) => { await deleteMessage(connection, message_id); await deleMentions(connection, message_id); connection.end(); + + if (sockets.size > 0) { + for (const client of sockets) { + if (client.readyState === 1) { + client.send('delete_message'); + } + } + } + + if (channelSockets.has(name)) { + for (const client of channelSockets.get(name)) { + if (client.readyState === 1) { + client.send('delete_message'); + } + } + } + res.send({ message: 'Message deleted' }); }); @@ -140,6 +200,15 @@ router.post('/add', async (req, res) => { await addChannel(connection, name, description, user.id); connection.end(); + + if (sockets.size > 0) { + for (const client of sockets) { + if (client.readyState === 1) { + client.send('new_channel'); + } + } + } + res.send({ message: 'Channel added' }); }); diff --git a/back/api/lastmessages.js b/back/api/lastmessages.js index 2de089d..8803ec6 100644 --- a/back/api/lastmessages.js +++ b/back/api/lastmessages.js @@ -1,5 +1,4 @@ const express = require('express'); -const jwt = require('jsonwebtoken'); const { getConnection, getLastMessages, getMentions } = require('../libs/mysql'); const router = express.Router(); diff --git a/back/api/newchannels.js b/back/api/newchannels.js new file mode 100644 index 0000000..2bdf456 --- /dev/null +++ b/back/api/newchannels.js @@ -0,0 +1,13 @@ +const express = require('express'); +const { getConnection, getNewChannels } = require('../libs/mysql'); + +const router = express.Router(); + +router.get('/', async (req, res) => { + const connection = await getConnection(); + const channels = await getNewChannels(connection); + connection.end(); + res.send(channels); +}); + +module.exports = router; \ No newline at end of file diff --git a/back/index.js b/back/index.js index 83877dc..aefd4a6 100644 --- a/back/index.js +++ b/back/index.js @@ -7,6 +7,7 @@ const cors = require("cors"); require("dotenv").config(); const app = express(); +var expressWs = require('express-ws')(app); const port = config.port || 3000; app.use(express.json()); diff --git a/back/libs/mysql.js b/back/libs/mysql.js index c108ac7..1f99fa8 100644 --- a/back/libs/mysql.js +++ b/back/libs/mysql.js @@ -110,7 +110,7 @@ function getActiveChannels(connection) { FROM messages JOIN channels ON messages.channel_id = channels.id JOIN users ON messages.user_id = users.id - WHERE date > (SELECT max(date) FROM messages) - 7 * 24 * 60 * 60 + WHERE date > (SELECT max(date) FROM messages) - 3 * 24 * 60 * 60 GROUP BY channel_id ORDER BY count(*) DESC LIMIT 5;`, @@ -124,6 +124,23 @@ function getActiveChannels(connection) { }); } +function getNewChannels(connection) { + return new Promise((resolve, reject) => { + connection.query( + `SELECT channels.id, name, description, owner_id, username AS owner_username + FROM channels + JOIN users ON channels.owner_id = users.id + ORDER BY channels.id DESC LIMIT 5`, + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + function searchChannels(connection, search) { return new Promise((resolve, reject) => { connection.query( @@ -319,6 +336,7 @@ module.exports = { getUserLastMessages, getChannels, getActiveChannels, + getNewChannels, searchChannels, getChannel, addChannel, diff --git a/back/package.json b/back/package.json index 3a629cf..9f40754 100644 --- a/back/package.json +++ b/back/package.json @@ -16,6 +16,7 @@ "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", + "express-ws": "^5.0.2", "fs": "^0.0.1-security", "https": "^1.0.0", "jsonwebtoken": "^9.0.2", diff --git a/front/package-lock.json b/front/package-lock.json index a6fd7ee..6580df6 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -1387,6 +1387,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.14.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", + "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/react": { "version": "19.0.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.12.tgz", @@ -3400,6 +3412,15 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", diff --git a/front/src/components/MessageComponent.tsx b/front/src/components/MessageComponent.tsx index 328886a..b02e407 100644 --- a/front/src/components/MessageComponent.tsx +++ b/front/src/components/MessageComponent.tsx @@ -19,6 +19,8 @@ export default function MessageComponent({ message, user, channel, deleteMessage if (mention) { return {word} ; } + } else if (word.startsWith("https://") || word.startsWith("http://")) { + return {word} } return {word} ; })} diff --git a/front/src/index.css b/front/src/index.css index 73dcf47..99b54d8 100644 --- a/front/src/index.css +++ b/front/src/index.css @@ -1,5 +1,6 @@ html { background: linear-gradient(#FFE0E3, #f59ebb); + min-height: 100vh; } .cat { diff --git a/front/src/pages/ChannelPage.tsx b/front/src/pages/ChannelPage.tsx index d747d5c..0f2c4c9 100644 --- a/front/src/pages/ChannelPage.tsx +++ b/front/src/pages/ChannelPage.tsx @@ -71,15 +71,17 @@ export default function ChannelPage() { }, [name]); useEffect(() => { - const id = setInterval(() => { - axios - .get(`/api/channels/${name}/messages`).then((res) => { - setMessages(res.data) - }) - }, 2000) - - return () => { clearInterval(id) } - }, []) + const socket = new WebSocket(`/api/channels/${name}`); + + socket.addEventListener('message', function (event) { + if (event.data === "new_message" || event.data === "delete_message") { + axios + .get(`/api/channels/${name}/messages`).then((res) => { + setMessages(res.data) + }) + } + }); + }, [name]) useEffect(() => { const words = message.toString().split(" "); @@ -102,7 +104,14 @@ export default function ChannelPage() { if (!channel || !messages) { - return
Loading...
+Loading...
+Admin
:User
} -