generated from lucien/api-template
add: added replies to messages
This commit is contained in:
parent
05dd75fa74
commit
b10a472ed7
10 changed files with 607 additions and 306 deletions
|
@ -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;
|
||||
|
|
|
@ -14,6 +14,7 @@ router.get('/', async (req, res) => {
|
|||
} else {
|
||||
message.mentions = [];
|
||||
}
|
||||
message.replies = [];
|
||||
}
|
||||
|
||||
connection.end();
|
||||
|
|
156
back/api/messages.js
Normal file
156
back/api/messages.js
Normal 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;
|
|
@ -37,6 +37,7 @@ router.get('/:username/lastmessages', async (req, res) => {
|
|||
} else {
|
||||
message.mentions = [];
|
||||
}
|
||||
message.replies = [];
|
||||
}
|
||||
|
||||
connection.end();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
import { Link } from "react-router-dom";
|
||||
import { Message, User, Channel } from "../types";
|
||||
import { Message, User, Channel, Users, Emojis } from "../types";
|
||||
import { useState, createRef, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
import "../styles/MessageComponent.css";
|
||||
|
||||
export default function MessageComponent({ message, user, channel, deleteMessage }: {
|
||||
export default function MessageComponent({ message, user, channel }: {
|
||||
message: Message;
|
||||
user: User | undefined;
|
||||
channel: Channel | undefined;
|
||||
deleteMessage: ((messageId: number) => void) | undefined;
|
||||
}) {
|
||||
const [reply, setReply] = useState(false);
|
||||
const token = localStorage.getItem("token");
|
||||
const [replyContent, setReplyContent] = useState("");
|
||||
const [searchedUsers, setSearchedUsers] = useState<Users>([]);
|
||||
const [searchedEmojis, setSearchedEmojis] = useState<Emojis>([]);
|
||||
const ref = createRef<HTMLInputElement>();
|
||||
|
||||
function getMessageArray(message: Message) {
|
||||
const array = []
|
||||
|
@ -38,6 +45,70 @@ export default function MessageComponent({ message, user, channel, deleteMessage
|
|||
array.push(string)
|
||||
return array.filter((word) => word !== '');
|
||||
}
|
||||
|
||||
function handleReply(e: React.FormEvent<HTMLFormElement>, messageId: number) {
|
||||
e.preventDefault();
|
||||
axios
|
||||
.post(`/api/messages/${messageId}/reply`, {
|
||||
token: token,
|
||||
message: replyContent,
|
||||
})
|
||||
.then(() => {
|
||||
setReplyContent("");
|
||||
setReply(false);
|
||||
setSearchedUsers([]);
|
||||
setSearchedEmojis([]);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err.response.data);
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
if (!window.confirm("Are you sure you want to delete this message?")) {
|
||||
return;
|
||||
}
|
||||
axios
|
||||
.post(`/api/messages/${message.id}/delete`, {
|
||||
token: token,
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err.response.data);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const words = replyContent.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.data);
|
||||
});
|
||||
} else {
|
||||
setSearchedUsers([]);
|
||||
}
|
||||
|
||||
if (lastWord && lastWord.startsWith(":")) {
|
||||
const emojiName = lastWord.slice(1);
|
||||
axios
|
||||
.get(`/api/searchemojis?search=${emojiName}`)
|
||||
.then((res) => {
|
||||
setSearchedEmojis(res.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err.response.data);
|
||||
});
|
||||
} else {
|
||||
setSearchedEmojis([]);
|
||||
}
|
||||
}, [replyContent]);
|
||||
|
||||
return (
|
||||
<div key={message.id} className="message">
|
||||
|
@ -70,13 +141,81 @@ export default function MessageComponent({ message, user, channel, deleteMessage
|
|||
<img src="/le_poisson_steve.png" alt="Le poisson steve" className="fish" />
|
||||
)}
|
||||
</div>
|
||||
{channel && deleteMessage ? (
|
||||
{channel?.owner_id == user?.id || user?.admin == 1 || user?.username == message.username ? (
|
||||
user?.id === message.user_id || user?.id === channel?.owner_id || user?.admin === 1) && (
|
||||
<button onClick={() => {deleteMessage(message.id)}}>Delete</button>
|
||||
) : (
|
||||
<p>In <Link to={`/c/${message.channel_name}`}>{message.channel_name}</Link></p>
|
||||
)
|
||||
}
|
||||
<button onClick={handleDelete}>Delete</button>
|
||||
) : (
|
||||
<p>In <Link to={`/c/${message.channel_name}`}>{message.channel_name}</Link></p>
|
||||
)}
|
||||
{channel && user && !reply && (
|
||||
<button onClick={() => setReply(true)}>Reply</button>
|
||||
)}
|
||||
{reply && (
|
||||
<form onSubmit={(e) => handleReply(e, message.id)} className="message-form">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Message"
|
||||
value={replyContent}
|
||||
onChange={(e) => setReplyContent(e.target.value)}
|
||||
ref={ref}
|
||||
/>
|
||||
<button type="submit">Send</button>
|
||||
{searchedUsers.length > 0 && (
|
||||
<div className="mentions">
|
||||
{searchedUsers.map((user) => (
|
||||
<div key={user.id} className="mention">
|
||||
<Link to={`/u/${user.username}`}>{user.username}</Link>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setReplyContent(
|
||||
replyContent.split(" ").slice(0, -1).join(" ") + " @" + user.username + " "
|
||||
);
|
||||
setSearchedUsers([]);
|
||||
ref.current?.focus();
|
||||
}}
|
||||
>
|
||||
Mention
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{searchedEmojis.length > 0 && (
|
||||
<div className="emojis">
|
||||
{searchedEmojis.map((emoji) => (
|
||||
<div key={emoji.id} className="search-emoji">
|
||||
<img src={`/api/emojis/${emoji.name}`} alt={emoji.name} className="emojis-emoji" />
|
||||
<span>:{emoji.name}:</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setReplyContent(
|
||||
replyContent.split(" ").slice(0, -1).join(" ") + " :" + emoji.name + ": "
|
||||
);
|
||||
setSearchedEmojis([]);
|
||||
ref.current?.focus();
|
||||
}}
|
||||
>
|
||||
Add Emoji
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<button onClick={() => setReply(false)}>Cancel</button>
|
||||
</form>
|
||||
)}
|
||||
{message.has_replies == true && (
|
||||
<div className="message-replies">
|
||||
{message.replies.map((reply) => (
|
||||
<MessageComponent message={reply} user={user} channel={channel} key={reply.id} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!channel && (
|
||||
<p>in: <Link to={`/c/${message.channel_name}`}>{message.channel_name}</Link></p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -34,14 +34,6 @@ export default function ChannelPage({socket}: {socket: WebSocket}) {
|
|||
});
|
||||
}
|
||||
|
||||
function deleteMessage(message_id: number) {
|
||||
axios
|
||||
.post(`/api/channels/${name}/messages/delete`, { token, message_id })
|
||||
.catch((err) => {
|
||||
console.error(err.response);
|
||||
});
|
||||
}
|
||||
|
||||
function purgeChannel() {
|
||||
if (!window.confirm(`Are you sure you want to purge ${channel?.name}?`)) {
|
||||
return;
|
||||
|
@ -261,7 +253,6 @@ export default function ChannelPage({socket}: {socket: WebSocket}) {
|
|||
message={message}
|
||||
user={user}
|
||||
channel={channel}
|
||||
deleteMessage={deleteMessage}
|
||||
/>
|
||||
))}
|
||||
{messages.length > maxMessageToShown && (
|
||||
|
|
|
@ -165,7 +165,6 @@ export default function Home({socket}: {socket: WebSocket}) {
|
|||
message={message}
|
||||
user={user}
|
||||
channel={undefined}
|
||||
deleteMessage={undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
.message {
|
||||
width: 95%;
|
||||
width: 97%;
|
||||
border: 1px solid #270722;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.message-user-pfp {
|
||||
|
@ -37,6 +42,15 @@
|
|||
max-height: 1em;
|
||||
}
|
||||
|
||||
.message-replies {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.message {
|
||||
border: 1px solid #a678af;
|
||||
|
|
|
@ -34,6 +34,8 @@ export type Message = {
|
|||
channel_id: number
|
||||
channel_name: string
|
||||
mentions: Mentions
|
||||
replies: Messages
|
||||
has_replies: boolean
|
||||
}
|
||||
|
||||
export type Messages = Message[]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue