add: added replies to messages

This commit is contained in:
Lukian 2025-05-11 21:10:46 +02:00
parent 05dd75fa74
commit b10a472ed7
10 changed files with 607 additions and 306 deletions

View file

@ -4,17 +4,18 @@ const {
getChannels,
getChannel,
addChannel,
getMessages,
getMessage,
getMessages,
addMessage,
deleteMessage,
addMention,
getMentions,
getUserByUsername,
deleMentions,
deleteChannelMentions,
deleteChannelMessages,
deleteChannel
deleteChannel,
setHasReplies,
addReply,
getMessageReplies
} = require('../libs/mysql');
const rateLimit = require("express-rate-limit");
const slowDown = require("express-slow-down");
@ -70,7 +71,6 @@ router.post('/:name/purge', async (req, res) => {
return res.status(401).send({ error: 'Unauthorized' });
}
await deleteChannelMentions(connection, channel[0].id);
await deleteChannelMessages(connection, channel[0].id);
connection.end();
@ -111,6 +111,23 @@ router.post('/:name/delete', async (req, res) => {
res.send({ message: 'Channel deleted' });
});
async function getReplies(message, connection) {
const replies = await getMessageReplies(connection, message.id);
for (const reply of replies) {
if (reply.has_replies) {
const subReplies = await getReplies(reply, connection);
reply.replies = subReplies;
}
if (reply.content.includes('@')) {
const mentions = await getMentions(connection, reply.id);
reply.mentions = mentions;
} else {
reply.mentions = [];
}
}
return replies;
}
router.get('/:name/messages', async (req, res) => {
const name = req.params.name;
const connection = await getConnection();
@ -128,6 +145,14 @@ router.get('/:name/messages', async (req, res) => {
} else {
message.mentions = [];
}
if (message.has_replies) {
const replies = await getReplies(message, connection);
message.replies = replies;
}
else {
message.replies = [];
}
}
connection.end();
@ -175,46 +200,6 @@ router.post('/:name/messages/send', speedLimiter, limiter, checkAuth, async (req
res.send({ message: 'Message sent' });
});
router.post('/:name/messages/delete', checkAuth, async (req, res) => {
const { message_id } = req.body;
const name = req.params.name;
const user = req.user;
if (!message_id) {
return res.status(400).send({ error: 'Missing message_id' });
}
const connection = await getConnection();
const message = await getMessage(connection, message_id);
if (!message[0]) {
connection.end();
return res.status(400).send({ error: 'No message found' });
}
const channel = await getChannel(connection, name);
if (!channel[0]) {
connection.end();
return res.status(400).send({ error: 'No channel found' });
}
if (user.id !== channel[0].owner_id && user.id !== message[0].user_id && user.admin !== 1) {
connection.end();
return res.status(401).send({ error: 'Unauthorized' });
}
await deleteMessage(connection, message_id);
connection.end();
req.sockets.emit({
type: 'delete_message',
channel_id: channel[0].id,
user_id: user.id,
});
res.send({ message: 'Message deleted' });
});
router.post('/add', speedLimiter, limiter, checkAuth, async (req, res) => {
const { name, description } = req.body;
const user = req.user;

View file

@ -14,6 +14,7 @@ router.get('/', async (req, res) => {
} else {
message.mentions = [];
}
message.replies = [];
}
connection.end();

156
back/api/messages.js Normal file
View file

@ -0,0 +1,156 @@
const express = require('express');
const {
getConnection,
getChannel,
getMessage,
deleteMessage,
addMention,
getMentions,
getUserByUsername,
setHasReplies,
addReply,
getMessageReplies
} = require('../libs/mysql');
const rateLimit = require("express-rate-limit");
const slowDown = require("express-slow-down");
const { checkAuth } = require('../libs/middlewares');
const limiter = rateLimit({
windowMs: 1 * 1000,
max: 2,
});
const speedLimiter = slowDown({
windowMs: 1 * 1000,
delayAfter: 2,
delayMs: () => 5000,
});
const router = express.Router();
async function getReplies(message, connection) {
const replies = await getMessageReplies(connection, message.id);
for (const reply of replies) {
if (reply.has_replies) {
const subReplies = await getReplies(reply, connection);
reply.replies = subReplies;
}
if (reply.content.includes('@')) {
const mentions = await getMentions(connection, reply.id);
reply.mentions = mentions;
} else {
reply.mentions = [];
}
}
return replies;
}
router.get('/:message_id', async (req, res) => {
const message_id = req.params.message_id;
const connection = await getConnection();
const message = await getMessage(connection, message_id);
if (!message[0]) {
connection.end();
return res.send({ error: 'No message found' });
}
if (message[0].content.includes('@')) {
const mentions = await getMentions(connection, message.id);
message[0].mentions = mentions;
} else {
message[0].mentions = [];
}
if (message[0].has_replies) {
const replies = await getReplies(message[0], connection);
message[0].replies = replies;
} else {
message[0].replies = [];
}
connection.end();
res.send(message[0]);
});
router.post('/:message_id/reply', speedLimiter, limiter, checkAuth, async (req, res) => {
const { message } = req.body;
const { message_id } = req.params;
const user = req.user;
if (!message) {
return res.status(400).send({ error: 'Missing parameters' });
}
const connection = await getConnection();
const originalMessage = await getMessage(connection, message_id);
if (!originalMessage[0]) {
connection.end();
return res.status(400).send({ error: 'No message found' });
}
const channel = await getChannel(connection, originalMessage[0].channel_name);
const sent_message = await addReply(connection, channel[0].id, user.id, message.replace("\"", "'"), message_id);
await setHasReplies(connection, message_id, true);
const new_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, new_message_id, mentionedUser[0].id);
}
}
}
connection.end();
req.sockets.emit({
type: 'new_message',
channel_id: channel[0].id,
user_id: user.id,
});
res.send({ message: 'Reply sent' });
});
router.post('/:message_id/delete', checkAuth, async (req, res) => {
const { message_id } = req.params;
const user = req.user;
const connection = await getConnection();
const message = await getMessage(connection, message_id);
if (!message[0]) {
connection.end();
return res.status(400).send({ error: 'No message found' });
}
const channel = await getChannel(connection, message[0].channel_name);
if (user.id !== channel[0].owner_id && user.id !== message[0].user_id && user.admin !== 1) {
connection.end();
return res.status(401).send({ error: 'Unauthorized' });
}
if (message[0].reply_to_id) {
const replies = await getMessageReplies(connection, message[0].reply_to_id);
if (replies.length === 1) {
await setHasReplies(connection, replyToId, false);
}
}
await deleteMessage(connection, message_id);
connection.end();
req.sockets.emit({
type: 'delete_message',
channel_id: channel[0].id,
user_id: user.id,
});
res.send({ message: 'Message deleted' });
});
module.exports = router;

View file

@ -37,6 +37,7 @@ router.get('/:username/lastmessages', async (req, res) => {
} else {
message.mentions = [];
}
message.replies = [];
}
connection.end();

View file

@ -9,6 +9,41 @@ function getConnection() {
});
}
// +---------------------------+
// | Users |
// +---------------------------+
function addUser(connection, username, password) {
return new Promise((resolve, reject) => {
connection.query(
`INSERT INTO users (username, password) VALUES (?, ?)`,
[username, password],
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function deleteUser(connection, id) {
return new Promise((resolve, reject) => {
connection.query(
`DELETE FROM users WHERE id = ?`,
[id],
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function getUsers(connection) {
return new Promise((resolve, reject) => {
connection.query(
@ -27,22 +62,7 @@ function getUser(connection, id) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT * FROM users WHERE id = ?`,
[id], // Use parameterized query
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function searchUser(connection, search) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT * FROM users WHERE username LIKE ? LIMIT 5`,
[`%${search}%`], // Use parameterized query
[id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -57,7 +77,7 @@ function getUserByUsername(connection, username) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT * FROM users WHERE username = ?`,
[username], // Use parameterized query
[username],
(error, result) => {
if (error) {
reject(new Error(error));
@ -68,11 +88,11 @@ function getUserByUsername(connection, username) {
});
}
function addUser(connection, username, password) {
function searchUser(connection, search) {
return new Promise((resolve, reject) => {
connection.query(
`INSERT INTO users (username, password) VALUES (?, ?)`,
[username, password], // Use parameterized query
`SELECT * FROM users WHERE username LIKE ? LIMIT 5`,
[`%${search}%`],
(error, result) => {
if (error) {
reject(new Error(error));
@ -83,46 +103,11 @@ function addUser(connection, username, password) {
});
}
function getUserLastMessages(connection, username) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT messages.id, user_id, username, content, date, channels.name AS channel_name
FROM messages
JOIN users ON messages.user_id = users.id
JOIN channels ON messages.channel_id = channels.id
WHERE username = ?
ORDER BY date DESC LIMIT 5`,
[username], // Use parameterized query
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function deleteUser(connection, id) {
return new Promise((resolve, reject) => {
connection.query(
`DELETE FROM users WHERE id = ?`,
[id], // Use parameterized query
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
};
function setUserPfp(connection, id, pfp) {
return new Promise((resolve, reject) => {
connection.query(
`UPDATE users SET pfp = ? WHERE id = ?`,
[pfp, id], // Use parameterized query
[pfp, id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -137,7 +122,7 @@ function setUserUsername(connection, id, username) {
return new Promise((resolve, reject) => {
connection.query(
`UPDATE users SET username = ? WHERE id = ?`,
[username, id], // Use parameterized query
[username, id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -152,7 +137,41 @@ function setUserPassword(connection, id, password) {
return new Promise((resolve, reject) => {
connection.query(
`UPDATE users SET password = ? WHERE id = ?`,
[password, id], // Use parameterized query
[password, id],
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
// +---------------------------+
// | Channels |
// +---------------------------+
function addChannel(connection, name, description, owner_id) {
return new Promise((resolve, reject) => {
connection.query(
`INSERT INTO channels (name, description, owner_id) VALUES (?, ?, ?)`,
[name, description, owner_id],
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function deleteChannel(connection, channel_id) {
return new Promise((resolve, reject) => {
connection.query(
`DELETE FROM channels WHERE id = ?`,
[channel_id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -177,6 +196,24 @@ function getChannels(connection) {
});
}
function getChannel(connection, name) {
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
WHERE name = ?`,
[name],
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function getActiveChannels(connection) {
return new Promise((resolve, reject) => {
connection.query(
@ -223,7 +260,7 @@ function searchChannels(connection, search) {
JOIN users ON channels.owner_id = users.id
WHERE name LIKE ?
LIMIT 5`,
[`%${search}%`], // Use parameterized query
[`%${search}%`],
(error, result) => {
if (error) {
reject(new Error(error));
@ -234,116 +271,30 @@ function searchChannels(connection, search) {
});
}
function getChannel(connection, name) {
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
WHERE name = ?`,
[name], // Use parameterized query
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function addChannel(connection, name, description, owner_id) {
return new Promise((resolve, reject) => {
connection.query(
`INSERT INTO channels (name, description, owner_id) VALUES (?, ?, ?)`,
[name, description, owner_id], // Use parameterized query
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function deleteChannel(connection, channel_id) {
return new Promise((resolve, reject) => {
connection.query(
`DELETE FROM channels WHERE id = ?`,
[channel_id], // Use parameterized query
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function getMessages(connection, channel_id) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT messages.id, user_id, username, content, date, channels.name AS channel_name
FROM messages
JOIN users ON messages.user_id = users.id
JOIN channels ON messages.channel_id = channels.id
WHERE channel_id = ?
ORDER BY date DESC`,
[channel_id], // Use parameterized query
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function getLastMessages(connection) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT messages.id, user_id, username, content, date, channels.name AS channel_name
FROM messages
JOIN users ON messages.user_id = users.id
JOIN channels ON messages.channel_id = channels.id
ORDER BY date DESC LIMIT 5`,
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function getMessage(connection, message_id) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT messages.id, user_id, username, content, date, channels.name AS channel_name
FROM messages
JOIN users ON messages.user_id = users.id
JOIN channels ON messages.channel_id = channels.id
WHERE messages.id = ?`,
[message_id], // Use parameterized query
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
// +---------------------------+
// | Messages |
// +---------------------------+
function addMessage(connection, channel_id, user_id, message) {
return new Promise((resolve, reject) => {
connection.query(
`INSERT INTO messages (channel_id, user_id, content, date) VALUES (?, ?, ?, ?)`,
[channel_id, user_id, message, Math.floor(Date.now() / 1000)], // Use parameterized query
[channel_id, user_id, message, Math.floor(Date.now() / 1000)],
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function addReply(connection, channel_id, user_id, message, message_id) {
return new Promise((resolve, reject) => {
connection.query(
`INSERT INTO messages (channel_id, user_id, content, date, reply_to_id) VALUES (?, ?, ?, ?, ?)`,
[channel_id, user_id, message, Math.floor(Date.now() / 1000), message_id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -358,7 +309,7 @@ function deleteMessage(connection, message_id) {
return new Promise((resolve, reject) => {
connection.query(
`DELETE FROM messages WHERE id = ?`,
[message_id], // Use parameterized query
[message_id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -373,7 +324,7 @@ function deleteChannelMessages(connection, channel_id) {
return new Promise((resolve, reject) => {
connection.query(
`DELETE FROM messages WHERE channel_id = ?`,
[channel_id], // Use parameterized query
[channel_id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -384,11 +335,16 @@ function deleteChannelMessages(connection, channel_id) {
});
}
function deleteUserMessages(connection, user_id) {
function getMessages(connection, channel_id) {
return new Promise((resolve, reject) => {
connection.query(
`DELETE FROM messages WHERE user_id = ?`,
[user_id], // Use parameterized query
`SELECT messages.id, user_id, username, content, date, channels.name AS channel_name, has_replies
FROM messages
JOIN users ON messages.user_id = users.id
JOIN channels ON messages.channel_id = channels.id
WHERE channel_id = ? AND reply_to_id IS NULL
ORDER BY date DESC`,
[channel_id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -399,11 +355,106 @@ function deleteUserMessages(connection, user_id) {
});
}
function getMessage(connection, message_id) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT messages.id, user_id, username, content, date, channels.name AS channel_name, has_replies
FROM messages
JOIN users ON messages.user_id = users.id
JOIN channels ON messages.channel_id = channels.id
WHERE messages.id = ?`,
[message_id],
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function getLastMessages(connection) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT messages.id, user_id, username, content, date, channels.name AS channel_name, has_replies
FROM messages
JOIN users ON messages.user_id = users.id
JOIN channels ON messages.channel_id = channels.id
ORDER BY date DESC LIMIT 5`,
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function getUserLastMessages(connection, username) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT messages.id, user_id, username, content, date, channels.name AS channel_name, has_replies
FROM messages
JOIN users ON messages.user_id = users.id
JOIN channels ON messages.channel_id = channels.id
WHERE username = ?
ORDER BY date DESC LIMIT 5`,
[username],
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function getMessageReplies(connection, message_id) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT messages.id, user_id, username, content, date, channels.name AS channel_name, has_replies
FROM messages
JOIN users ON messages.user_id = users.id
JOIN channels ON messages.channel_id = channels.id
WHERE reply_to_id = ?`,
[message_id],
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function setHasReplies(connection, message_id, has_replies) {
return new Promise((resolve, reject) => {
connection.query(
`UPDATE messages SET has_replies = ? WHERE id = ?`,
[has_replies, message_id],
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
// +---------------------------+
// | Mentions |
// +---------------------------+
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
[message_id, user_id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -420,7 +471,7 @@ function getMentions(connection, message_id) {
`SELECT users.username FROM mentions
JOIN users ON mentions.user_id = users.id
WHERE message_id = ?`,
[message_id], // Use parameterized query
[message_id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -431,11 +482,15 @@ function getMentions(connection, message_id) {
});
}
function deleMentions(connection, message_id) {
// +---------------------------+
// | Emojis |
// +---------------------------+
function addEmoji(connection, name, file) {
return new Promise((resolve, reject) => {
connection.query(
`DELETE FROM mentions WHERE message_id = ?`,
[message_id], // Use parameterized query
`INSERT INTO emojis (name, file) VALUES (?, ?)`,
[name, file],
(error, result) => {
if (error) {
reject(new Error(error));
@ -446,27 +501,11 @@ function deleMentions(connection, message_id) {
});
}
function deleteChannelMentions(connection, channel_id) {
function deleteEmoji(connection, id) {
return new Promise((resolve, reject) => {
connection.query(
`DELETE FROM mentions WHERE message_id IN
(SELECT id FROM messages WHERE channel_id = ?)`,
[channel_id], // Use parameterized query
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function deleteUserMentions(connection, user_id) {
return new Promise((resolve, reject) => {
connection.query(
`DELETE FROM mentions WHERE user_id = ?`,
[user_id], // Use parameterized query
`DELETE FROM emojis WHERE id = ?`,
[id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -491,41 +530,11 @@ function getEmojis(connection) {
});
}
function addEmoji(connection, name, file) {
return new Promise((resolve, reject) => {
connection.query(
`INSERT INTO emojis (name, file) VALUES (?, ?)`,
[name, file], // Use parameterized query
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function deleteEmoji(connection, id) {
return new Promise((resolve, reject) => {
connection.query(
`DELETE FROM emojis WHERE id = ?`,
[id], // Use parameterized query
(error, result) => {
if (error) {
reject(new Error(error));
}
resolve(result);
}
);
});
}
function getEmoji(connection, id) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT * FROM emojis WHERE id = ?`,
[id], // Use parameterized query
[id],
(error, result) => {
if (error) {
reject(new Error(error));
@ -540,7 +549,7 @@ function getEmojiByName(connection, name) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT * FROM emojis WHERE name = ?`,
[name], // Use parameterized query
[name],
(error, result) => {
if (error) {
reject(new Error(error));
@ -555,7 +564,7 @@ function searchEmojis(connection, search) {
return new Promise((resolve, reject) => {
connection.query(
`SELECT * FROM emojis WHERE name LIKE ? LIMIT 5`,
[`%${search}%`], // Use parameterized query
[`%${search}%`],
(error, result) => {
if (error) {
reject(new Error(error));
@ -567,39 +576,43 @@ function searchEmojis(connection, search) {
}
module.exports = {
getUsers,
getConnection,
addUser,
deleteUser,
getUsers,
getUser,
searchUser,
getUserByUsername,
addUser,
deleteUser,
setUserPfp,
setUserUsername,
setUserPassword,
getUserLastMessages,
addChannel,
deleteChannel,
getChannels,
getChannel,
getActiveChannels,
getNewChannels,
searchChannels,
getChannel,
addChannel,
deleteChannel,
getMessages,
getLastMessages,
getMessage,
addMessage,
addReply,
deleteMessage,
deleteChannelMessages,
deleteUserMessages,
getMessages,
getMessage,
getUserLastMessages,
getLastMessages,
getMessageReplies,
setHasReplies,
addMention,
getMentions,
deleMentions,
deleteUserMentions,
deleteChannelMentions,
getEmojis,
addEmoji,
deleteEmoji,
getEmojis,
getEmoji,
getEmojiByName,
searchEmojis,