diff --git a/back/api/channels.js b/back/api/channels.js index 6fd865f..9055c15 100644 --- a/back/api/channels.js +++ b/back/api/channels.js @@ -1,5 +1,5 @@ const express = require('express'); -const { getConnection, getChannels, getChannel, addChannel, getMessages, getMessage, addMessage, deleteMessage, getLastMessages } = require('../libs/mysql'); +const { getConnection, getChannels, getChannel, addChannel, getMessages, getMessage, addMessage, deleteMessage, addMention, getMentions, getUserByUsername, deleMentions } = require('../libs/mysql'); const { checkAuth } = require('../libs/middlewares'); const router = express.Router(); @@ -32,6 +32,16 @@ router.get('/:name/messages', async (req, res) => { return res.send('No channel found'); } const messages = await getMessages(connection, channel[0].id); + + for (const message of messages) { + if (message.content.includes('@')) { + const mentions = await getMentions(connection, message.id); + message.mentions = mentions; + } else { + message.mentions = []; + } + } + connection.end(); res.send(messages); }); @@ -54,7 +64,19 @@ router.post('/:name/messages/send', async (req, res) => { return res.send('No channel found'); } - await addMessage(connection, channel[0].id, user.id, message.replace("\"", "'")); + const sent_message = await addMessage(connection, channel[0].id, user.id, message.replace("\"", "'")); + const message_id = sent_message.insertId; + + for (const word of message.split(' ')) { + if (word.startsWith('@')) { + const username = word.substring(1); + const mentionedUser = await getUserByUsername(connection, username); + if (mentionedUser[0]) { + await addMention(connection, message_id, mentionedUser[0].id); + } + } + } + connection.end(); res.send({ message: 'Message sent' }); }); @@ -89,6 +111,7 @@ router.post('/:name/messages/delete', async (req, res) => { } await deleteMessage(connection, message_id); + await deleMentions(connection, message_id); connection.end(); res.send({ message: 'Message deleted' }); }); diff --git a/back/api/lastmessages.js b/back/api/lastmessages.js index 745b1f7..2de089d 100644 --- a/back/api/lastmessages.js +++ b/back/api/lastmessages.js @@ -1,12 +1,22 @@ const express = require('express'); const jwt = require('jsonwebtoken'); -const { getConnection, getLastMessages } = require('../libs/mysql'); +const { getConnection, getLastMessages, getMentions } = require('../libs/mysql'); const router = express.Router(); router.get('/', async (req, res) => { const connection = await getConnection(); const messages = await getLastMessages(connection); + + for (const message of messages) { + if (message.content.includes('@')) { + const mentions = await getMentions(connection, message.id); + message.mentions = mentions; + } else { + message.mentions = []; + } + } + connection.end(); res.send(messages); }); diff --git a/back/api/searchuser.js b/back/api/searchuser.js new file mode 100644 index 0000000..cbb23d3 --- /dev/null +++ b/back/api/searchuser.js @@ -0,0 +1,14 @@ +const express = require('express'); +const { getConnection, searchUser } = require('../libs/mysql'); + +const router = express.Router(); + +router.get('/', async (req, res) => { + const { search } = req.query; + const connection = await getConnection(); + const users = await searchUser(connection, search); + connection.end(); + res.send(users); +}); + +module.exports = router; \ No newline at end of file diff --git a/back/api/users.js b/back/api/users.js index bb9e705..6d6450a 100644 --- a/back/api/users.js +++ b/back/api/users.js @@ -1,5 +1,5 @@ const express = require('express'); -const { getConnection, getUserByUsername, getUserLastMessages } = require('../libs/mysql'); +const { getConnection, getUserByUsername, getUserLastMessages, getMentions } = require('../libs/mysql'); const router = express.Router(); @@ -19,6 +19,16 @@ router.get('/:username/lastmessages', async (req, res) => { const username = req.params.username; const connection = await getConnection(); const messages = await getUserLastMessages(connection, username); + + for (const message of messages) { + if (message.content.includes('@')) { + const mentions = await getMentions(connection, message.id); + message.mentions = mentions; + } else { + message.mentions = []; + } + } + connection.end(); res.send(messages); }); diff --git a/back/libs/mysql.js b/back/libs/mysql.js index fd6cb48..c108ac7 100644 --- a/back/libs/mysql.js +++ b/back/libs/mysql.js @@ -24,6 +24,21 @@ function getUser(connection, id) { }); } +function searchUser(connection, search) { + return new Promise((resolve, reject) => { + connection.query( + `SELECT * FROM users WHERE username LIKE ? LIMIT 5`, + [`%${search}%`], // Use parameterized query + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + function getUserByUsername(connection, username) { return new Promise((resolve, reject) => { connection.query( @@ -248,9 +263,57 @@ function deleteMessage(connection, message_id) { }); } +function addMention(connection, message_id, user_id) { + return new Promise((resolve, reject) => { + connection.query( + `INSERT INTO mentions (message_id, user_id) VALUES (?, ?)`, + [message_id, user_id], // Use parameterized query + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function getMentions(connection, message_id) { + return new Promise((resolve, reject) => { + connection.query( + `SELECT users.username FROM mentions + JOIN users ON mentions.user_id = users.id + WHERE message_id = ?`, + [message_id], // Use parameterized query + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function deleMentions(connection, message_id) { + return new Promise((resolve, reject) => { + connection.query( + `DELETE FROM mentions WHERE message_id = ?`, + [message_id], // Use parameterized query + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + module.exports = { getConnection, getUser, + searchUser, getUserByUsername, addUser, getUserLastMessages, @@ -263,5 +326,8 @@ module.exports = { getLastMessages, getMessage, addMessage, - deleteMessage + deleteMessage, + addMention, + getMentions, + deleMentions, }; diff --git a/front/src/pages/ChannelPage.tsx b/front/src/pages/ChannelPage.tsx index 886727e..51605a4 100644 --- a/front/src/pages/ChannelPage.tsx +++ b/front/src/pages/ChannelPage.tsx @@ -11,6 +11,7 @@ export default function ChannelPage() { const [user, setUser] = useState(); const [message, setMessage] = useState(""); const [maxMessageToShown, setMaxMessageToShown] = useState(10); + const [searchedUsers, setSearchedUsers] = useState([]); function handleSubmit(e: React.FormEvent) { e.preventDefault(); @@ -75,6 +76,26 @@ export default function ChannelPage() { return () => { clearInterval(id) } }, []) + useEffect(() => { + const words = message.toString().split(" "); + const lastWord = words[words.length - 1]; + + if (lastWord && lastWord.startsWith("@")) { + const username = lastWord.slice(1); + axios + .get(`/api/searchuser?search=${username}`) + .then((res) => { + setSearchedUsers(res.data); + }) + .catch((err) => { + console.error(err.response); + }); + } else { + setSearchedUsers([]); + } + }, [message]); + + if (!channel || !messages) { return
Loading...
; } @@ -91,6 +112,27 @@ export default function ChannelPage() { { token ? (
+ {searchedUsers.length > 0 && ( +
+

Mentions:

+ {searchedUsers.map((user) => ( +
+ {user.username} + +
+ ))} +
+ )} {messages.slice(0, maxMessageToShown).map((message) => (
  • -

    {message.username}: {message.content}

    + {message.username}:{" "} + {message.content.split(" ").map((word, index) => { + if (word.startsWith("@")) { + const mention = message.mentions.find((mention) => `@${mention.username}` === word); + if (mention) { + return {word}; + } + } + return {word} ; + })}

    {new Date(message.date * 1000).toLocaleString()}

    {(user?.id === message.user_id || user?.id === channel.owner_id || user?.admin === 1) && ( diff --git a/front/src/pages/Home.tsx b/front/src/pages/Home.tsx index 2ae2b58..c09c973 100644 --- a/front/src/pages/Home.tsx +++ b/front/src/pages/Home.tsx @@ -27,7 +27,7 @@ export default function Home() { axios .get("/api/activechannels") - .then((res) => { + .then((res) => {channels setChannels(res.data) }) .catch((err) => { @@ -123,7 +123,16 @@ export default function Home() {
      {messages?.map((message) => (
    • -

      {message.username}: {message.content}

      + {message.username}:{" "} + {message.content.split(" ").map((word, index) => { + if (word.startsWith("@")) { + const mention = message.mentions.find((mention) => `@${mention.username}` === word); + if (mention) { + return {word}; + } + } + return {word} ; + })}

      In {message.channel_name}

      {new Date(message.date * 1000).toLocaleString()}

    • diff --git a/front/src/pages/UserPage.tsx b/front/src/pages/UserPage.tsx index 33aea66..33f45bc 100644 --- a/front/src/pages/UserPage.tsx +++ b/front/src/pages/UserPage.tsx @@ -42,7 +42,16 @@ export default function UserPage() {
        {messages?.map((message) => (
      • -

        {message.username}: {message.content}

        + {message.username}:{" "} + {message.content.split(" ").map((word, index) => { + if (word.startsWith("@")) { + const mention = message.mentions.find((mention) => `@${mention.username}` === word); + if (mention) { + return {word}; + } + } + return {word} ; + })}

        In {message.channel_name}

        {new Date(message.date * 1000).toLocaleString()}

      • diff --git a/front/src/types.tsx b/front/src/types.tsx index bc772d3..a5f44e1 100644 --- a/front/src/types.tsx +++ b/front/src/types.tsx @@ -19,6 +19,12 @@ export type RecentChannel = { export type RecentChannels = RecentChannel[] +export type Mention = { + username: string +} + +export type Mentions = Mention[] + export type Message = { id: number, user_id: number, @@ -27,6 +33,7 @@ export type Message = { date: number, channel_id: number, channel_name: string + mentions: Mentions } export type Messages = Message[]