add: added mentions

This commit is contained in:
Lukian 2025-04-05 17:33:23 +02:00
parent dfb6639ecf
commit 6c8f354bf6
9 changed files with 208 additions and 9 deletions

View file

@ -1,5 +1,5 @@
const express = require('express'); 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 { checkAuth } = require('../libs/middlewares');
const router = express.Router(); const router = express.Router();
@ -32,6 +32,16 @@ router.get('/:name/messages', async (req, res) => {
return res.send('No channel found'); return res.send('No channel found');
} }
const messages = await getMessages(connection, channel[0].id); 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(); connection.end();
res.send(messages); res.send(messages);
}); });
@ -54,7 +64,19 @@ router.post('/:name/messages/send', async (req, res) => {
return res.send('No channel found'); 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(); connection.end();
res.send({ message: 'Message sent' }); res.send({ message: 'Message sent' });
}); });
@ -89,6 +111,7 @@ router.post('/:name/messages/delete', async (req, res) => {
} }
await deleteMessage(connection, message_id); await deleteMessage(connection, message_id);
await deleMentions(connection, message_id);
connection.end(); connection.end();
res.send({ message: 'Message deleted' }); res.send({ message: 'Message deleted' });
}); });

View file

@ -1,12 +1,22 @@
const express = require('express'); const express = require('express');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const { getConnection, getLastMessages } = require('../libs/mysql'); const { getConnection, getLastMessages, getMentions } = require('../libs/mysql');
const router = express.Router(); const router = express.Router();
router.get('/', async (req, res) => { router.get('/', async (req, res) => {
const connection = await getConnection(); const connection = await getConnection();
const messages = await getLastMessages(connection); 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(); connection.end();
res.send(messages); res.send(messages);
}); });

14
back/api/searchuser.js Normal file
View file

@ -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;

View file

@ -1,5 +1,5 @@
const express = require('express'); const express = require('express');
const { getConnection, getUserByUsername, getUserLastMessages } = require('../libs/mysql'); const { getConnection, getUserByUsername, getUserLastMessages, getMentions } = require('../libs/mysql');
const router = express.Router(); const router = express.Router();
@ -19,6 +19,16 @@ router.get('/:username/lastmessages', async (req, res) => {
const username = req.params.username; const username = req.params.username;
const connection = await getConnection(); const connection = await getConnection();
const messages = await getUserLastMessages(connection, username); 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(); connection.end();
res.send(messages); res.send(messages);
}); });

View file

@ -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) { function getUserByUsername(connection, username) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
connection.query( 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 = { module.exports = {
getConnection, getConnection,
getUser, getUser,
searchUser,
getUserByUsername, getUserByUsername,
addUser, addUser,
getUserLastMessages, getUserLastMessages,
@ -263,5 +326,8 @@ module.exports = {
getLastMessages, getLastMessages,
getMessage, getMessage,
addMessage, addMessage,
deleteMessage deleteMessage,
addMention,
getMentions,
deleMentions,
}; };

View file

@ -11,6 +11,7 @@ export default function ChannelPage() {
const [user, setUser] = useState<User>(); const [user, setUser] = useState<User>();
const [message, setMessage] = useState<string>(""); const [message, setMessage] = useState<string>("");
const [maxMessageToShown, setMaxMessageToShown] = useState<number>(10); const [maxMessageToShown, setMaxMessageToShown] = useState<number>(10);
const [searchedUsers, setSearchedUsers] = useState<User[]>([]);
function handleSubmit(e: React.FormEvent) { function handleSubmit(e: React.FormEvent) {
e.preventDefault(); e.preventDefault();
@ -75,6 +76,26 @@ export default function ChannelPage() {
return () => { clearInterval(id) } 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) { if (!channel || !messages) {
return <div>Loading...</div>; return <div>Loading...</div>;
} }
@ -91,6 +112,27 @@ export default function ChannelPage() {
{ {
token ? ( token ? (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
{searchedUsers.length > 0 && (
<div>
<p>Mentions:</p>
{searchedUsers.map((user) => (
<div key={user.id}>
<Link to={`/u/${user.username}`}>{user.username}</Link>
<button
type="button"
onClick={() => {
setMessage(
message.split(" ").slice(0, -1).join(" ") + " @" + user.username + " "
);
setSearchedUsers([]);
}}
>
Mention
</button>
</div>
))}
</div>
)}
<input <input
type="text" type="text"
placeholder="Message" placeholder="Message"
@ -109,7 +151,16 @@ export default function ChannelPage() {
<ul> <ul>
{messages.slice(0, maxMessageToShown).map((message) => ( {messages.slice(0, maxMessageToShown).map((message) => (
<li key={message.id}> <li key={message.id}>
<p><Link to={`/u/${message.username}`}>{message.username}</Link>: {message.content}</p> <Link to={`/u/${message.username}`}>{message.username}</Link>:{" "}
{message.content.split(" ").map((word, index) => {
if (word.startsWith("@")) {
const mention = message.mentions.find((mention) => `@${mention.username}` === word);
if (mention) {
return <Link key={index} to={`/u/${mention.username}`}>{word}</Link>;
}
}
return <span key={index}>{word} </span>;
})}
<p>{new Date(message.date * 1000).toLocaleString()}</p> <p>{new Date(message.date * 1000).toLocaleString()}</p>
{(user?.id === message.user_id || user?.id === channel.owner_id || user?.admin === 1) && ( {(user?.id === message.user_id || user?.id === channel.owner_id || user?.admin === 1) && (
<button onClick={() => {deleteMessage(message.id)}}>Delete</button> <button onClick={() => {deleteMessage(message.id)}}>Delete</button>

View file

@ -27,7 +27,7 @@ export default function Home() {
axios axios
.get("/api/activechannels") .get("/api/activechannels")
.then((res) => { .then((res) => {channels
setChannels(res.data) setChannels(res.data)
}) })
.catch((err) => { .catch((err) => {
@ -123,7 +123,16 @@ export default function Home() {
<ul> <ul>
{messages?.map((message) => ( {messages?.map((message) => (
<li key={message.id}> <li key={message.id}>
<p><Link to={`/u/${message.username}`}>{message.username}</Link>: {message.content}</p> <Link to={`/u/${message.username}`}>{message.username}</Link>:{" "}
{message.content.split(" ").map((word, index) => {
if (word.startsWith("@")) {
const mention = message.mentions.find((mention) => `@${mention.username}` === word);
if (mention) {
return <Link key={index} to={`/u/${mention.username}`}>{word}</Link>;
}
}
return <span key={index}>{word} </span>;
})}
<p>In <Link to={`/c/${message.channel_name}`}>{message.channel_name}</Link></p> <p>In <Link to={`/c/${message.channel_name}`}>{message.channel_name}</Link></p>
<p>{new Date(message.date * 1000).toLocaleString()}</p> <p>{new Date(message.date * 1000).toLocaleString()}</p>
</li> </li>

View file

@ -42,7 +42,16 @@ export default function UserPage() {
<ul> <ul>
{messages?.map((message) => ( {messages?.map((message) => (
<li key={message.id}> <li key={message.id}>
<p><Link to={`/u/${message.username}`}>{message.username}</Link>: {message.content}</p> <Link to={`/u/${message.username}`}>{message.username}</Link>:{" "}
{message.content.split(" ").map((word, index) => {
if (word.startsWith("@")) {
const mention = message.mentions.find((mention) => `@${mention.username}` === word);
if (mention) {
return <Link key={index} to={`/u/${mention.username}`}>{word}</Link>;
}
}
return <span key={index}>{word} </span>;
})}
<p>In <Link to={`/c/${message.channel_name}`}>{message.channel_name}</Link></p> <p>In <Link to={`/c/${message.channel_name}`}>{message.channel_name}</Link></p>
<p>{new Date(message.date * 1000).toLocaleString()}</p> <p>{new Date(message.date * 1000).toLocaleString()}</p>
</li> </li>

View file

@ -19,6 +19,12 @@ export type RecentChannel = {
export type RecentChannels = RecentChannel[] export type RecentChannels = RecentChannel[]
export type Mention = {
username: string
}
export type Mentions = Mention[]
export type Message = { export type Message = {
id: number, id: number,
user_id: number, user_id: number,
@ -27,6 +33,7 @@ export type Message = {
date: number, date: number,
channel_id: number, channel_id: number,
channel_name: string channel_name: string
mentions: Mentions
} }
export type Messages = Message[] export type Messages = Message[]