Skip to content

Commit 2fefce1

Browse files
Azaldin FreidoonAzaldin Freidoon
Azaldin Freidoon
authored and
Azaldin Freidoon
committed
Initial project submission.
0 parents  commit 2fefce1

27 files changed

+1060
-0
lines changed

config/database.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require('dotenv').config();
2+
3+
// config/database.js
4+
const { Sequelize } = require('sequelize');
5+
6+
const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASSWORD, {
7+
host: process.env.DB_HOST,
8+
port: process.env.DB_PORT,
9+
dialect: 'postgres',
10+
logging: false
11+
});
12+
13+
// Test the connection
14+
async function testConnection() {
15+
try {
16+
await sequelize.authenticate();
17+
console.log('Database connection has been established successfully.');
18+
} catch (error) {
19+
console.error('Unable to connect to the database:', error);
20+
}
21+
}
22+
23+
testConnection();
24+
25+
module.exports = sequelize;

controllers/authController.js

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const bcrypt = require('bcrypt');
2+
const jwt = require('jsonwebtoken');
3+
const { User } = require('../models');
4+
5+
exports.register = async (req, res) => {
6+
try {
7+
const { username, email, password } = req.body;
8+
9+
if (!username || !email) {
10+
return res.status(400).json({ error: 'Username and email are required!' });
11+
}
12+
13+
const user = await User.create({
14+
username,
15+
email,
16+
password
17+
});
18+
19+
// Remove sensitive data from response
20+
const userResponse = {
21+
user_id: user.user_id,
22+
username: user.username,
23+
email: user.email,
24+
created_at: user.created_at,
25+
status: user.status
26+
};
27+
28+
res.status(201).json(userResponse);
29+
} catch (error) {
30+
if (error.name === 'SequelizeValidationError' || error.name === 'SequelizeUniqueConstraintError') {
31+
return res.status(400).json({ error: error.errors[0].message });
32+
}
33+
res.status(400).json({ error: error.message });
34+
}
35+
};
36+
37+
exports.login = async (req, res) => {
38+
try {
39+
const { username, email, password } = req.body;
40+
41+
let user;
42+
if (username) {
43+
user = await User.findOne({ where: { username } });
44+
} else if (email) {
45+
user = await User.findOne({ where: { email } });
46+
} else {
47+
return res.status(401).json({ error: 'Invalid credentials' });
48+
}
49+
50+
if (!user) {
51+
return res.status(401).json({ error: 'Invalid credentials' });
52+
}
53+
54+
user.password = password;
55+
const isValid = await user.validatePassword(password);
56+
if (!isValid) {
57+
return res.status(401).json({ error: 'Invalid credentials' });
58+
}
59+
60+
const token = jwt.sign({ userId: user.user_id }, process.env.JWT_SECRET, { expiresIn: '1d' });
61+
62+
res.json({
63+
user: {
64+
user_id: user.user_id,
65+
username: user.username,
66+
email: user.email
67+
},
68+
token: token
69+
});
70+
} catch (error) {
71+
res.status(500).json({ error: error.message });
72+
}
73+
};

controllers/channelController.js

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
const { Channel, UserChannel, ChannelInvitation, User } = require('../models');
2+
3+
exports.createChannel = async (req, res) => {
4+
try {
5+
const { channelName, isPrivate } = req.body;
6+
const channel = await Channel.create({ channel_name: channelName, is_private: isPrivate });
7+
await UserChannel.create({ user_id: req.userData.userId, channel_id: channel.channel_id });
8+
res.status(201).json(channel);
9+
} catch (error) {
10+
res.status(500).json({ message: 'Error creating channel', error: error.message });
11+
}
12+
};
13+
14+
exports.getChannels = async (req, res) => {
15+
try {
16+
const channels = await Channel.findAll({
17+
include: [
18+
{
19+
model: User,
20+
where: { user_id: req.userData.userId },
21+
attributes: [],
22+
through: { attributes: [] },
23+
},
24+
],
25+
});
26+
res.json(channels);
27+
} catch (error) {
28+
res.status(500).json({ message: 'Error fetching channels', error: error.message });
29+
}
30+
};
31+
32+
exports.getInvitations = async (req, res) => {
33+
try {
34+
const invitations = await ChannelInvitation.findAll({ where: { invitee_id: req.userData.userId } });
35+
res.json(invitations);
36+
} catch (error) {
37+
res.status(500).json({ message: 'Error fetching invitations', error: error.message });
38+
}
39+
};
40+
41+
exports.inviteToChannel = async (req, res) => {
42+
try {
43+
const { channelId, inviteeId } = req.body;
44+
const userId = req.userData.userId;
45+
46+
// Check if user is inviting themselves
47+
if (userId === inviteeId) {
48+
return res.status(400).json({ message: 'Cannot invite yourself to a channel' });
49+
}
50+
51+
// Check if invitee exists
52+
const invitee = await User.findByPk(inviteeId);
53+
if (!invitee) {
54+
return res.status(404).json({ message: 'Invitee not found' });
55+
}
56+
57+
// Check if user is a member of the channel
58+
const userChannel = await UserChannel.findOne({
59+
where: {
60+
channel_id: channelId,
61+
user_id: userId
62+
}
63+
});
64+
65+
if (!userChannel) {
66+
return res.status(403).json({ message: 'You must be a member of the channel to invite others' });
67+
}
68+
69+
const inviteeChannel = await UserChannel.findOne({
70+
where: {
71+
channel_id: channelId,
72+
user_id: inviteeId
73+
}
74+
});
75+
76+
if (inviteeChannel) {
77+
return res.status(400).json({ message: 'Invitee is already a member of the channel' });
78+
}
79+
80+
await ChannelInvitation.create({
81+
channel_id: channelId,
82+
inviter_id: req.userData.userId,
83+
invitee_id: inviteeId,
84+
});
85+
86+
await userChannel.update({ is_private: false });
87+
res.status(201).json({ message: 'Invitation sent successfully' });
88+
} catch (error) {
89+
res.status(500).json({ message: 'Error sending invitation', error: error.message });
90+
}
91+
};
92+
93+
exports.acceptInvitation = async (req, res) => {
94+
try {
95+
const { invitationId } = req.body;
96+
97+
// Check if invitationId is provided
98+
if (!invitationId) {
99+
return res.status(400).json({ message: 'Invitation ID is required' });
100+
}
101+
102+
const invitation = await ChannelInvitation.findByPk(invitationId);
103+
if (!invitation || invitation.invitee_id !== req.userData.userId) {
104+
return res.status(403).json({ message: 'Invalid invitation' });
105+
}
106+
107+
// Mark invitation as accepted
108+
await invitation.update({ accepted: true });
109+
110+
// Check if user already member of channel
111+
const userChannel = await UserChannel.findOne({
112+
where: {
113+
user_id: req.userData.userId,
114+
channel_id: invitation.channel_id
115+
}
116+
});
117+
118+
if (userChannel) {
119+
return res.json({ message: 'Invitation accepted successfully' });
120+
}
121+
122+
await UserChannel.create({ user_id: req.userData.userId, channel_id: invitation.channel_id });
123+
res.json({ message: 'Invitation accepted successfully' });
124+
} catch (error) {
125+
res.status(500).json({ message: 'Error accepting invitation', error: error.message });
126+
}
127+
};

controllers/fileController.js

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const { File, SearchIndexFile, Message } = require('../models');
2+
const { Op, Sequelize } = require('sequelize');
3+
4+
exports.uploadFile = async (req, res) => {
5+
try {
6+
const { messageId, fileName, filePath, fileType } = req.body;
7+
const file = await File.create({
8+
message_id: messageId,
9+
file_name: fileName,
10+
file_path: filePath,
11+
file_type: fileType,
12+
});
13+
// Index file for search
14+
const terms = fileName.toLowerCase().split(/\W+/);
15+
await Promise.all(terms.map(term =>
16+
SearchIndexFile.create({ file_id: file.file_id, term })
17+
));
18+
res.status(201).json(file);
19+
} catch (error) {
20+
res.status(500).json({ message: 'Error uploading file', error: error.message });
21+
}
22+
};
23+
24+
exports.searchFiles = async (req, res) => {
25+
try {
26+
const { q: searchQuery } = req.query;
27+
28+
// Check if search query is provided
29+
if (!searchQuery || typeof searchQuery !== 'string') {
30+
return res.status(400).json({ message: 'Search query parameter "q" is required' });
31+
}
32+
33+
// Sanitize and prepare search terms
34+
const terms = searchQuery.toLowerCase()
35+
.trim()
36+
.split(/\W+/)
37+
.filter(term => term.length > 0);
38+
39+
if (terms.length === 0) {
40+
return res.status(400).json({ message: 'Search query must contain valid terms' });
41+
}
42+
43+
const fileIds = await SearchIndexFile.findAll({
44+
where: {
45+
term: { [Op.in]: terms }
46+
},
47+
attributes: ['file_id'],
48+
group: ['file_id'],
49+
having: Sequelize.literal(`COUNT(DISTINCT term) = ${terms.length}`),
50+
});
51+
52+
const files = await File.findAll({
53+
where: {
54+
file_id: { [Op.in]: fileIds.map(f => f.file_id) }
55+
},
56+
include: [{
57+
model: Message,
58+
attributes: ['message_id', 'sender_id']
59+
}],
60+
order: [['uploaded_at', 'DESC']],
61+
limit: 50 // Prevent excessive results
62+
});
63+
64+
res.json({
65+
results: files,
66+
count: files.length,
67+
query: searchQuery
68+
});
69+
70+
} catch (error) {
71+
res.status(500).json({ message: 'Error searching files', error: error.message });
72+
}
73+
};

controllers/messageController.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const { Message, User } = require('../models');
2+
3+
exports.getThreadMessages = async (req, res) => {
4+
try {
5+
const { parentMessageId } = req.params;
6+
7+
// Check if parentMessageId is provided
8+
if (!parentMessageId) {
9+
return res.status(400).json({ message: 'Parent message ID is required' });
10+
}
11+
12+
const messages = await Message.findAll({
13+
where: { parent_message_id: parentMessageId },
14+
include: [{
15+
model: User,
16+
as: 'sender',
17+
attributes: ['user_id', 'username'] // Include relevant user fields
18+
}],
19+
order: [['sent_at', 'ASC']],
20+
});
21+
22+
res.json(messages);
23+
} catch (error) {
24+
res.status(500).json({ message: 'Error fetching thread messages', error: error.message });
25+
}
26+
};
27+
28+
exports.createMessage = async (req, res) => {
29+
try {
30+
const { channel_id, content, parent_message_id } = req.body;
31+
const sender_id = req.userData.userId;
32+
let message;
33+
34+
if (parent_message_id) {
35+
message = await Message.create({ channel_id, sender_id, content, parent_message_id });
36+
} else {
37+
message = await Message.create({ channel_id, sender_id, content });
38+
}
39+
res.status(201).json(message);
40+
} catch (error) {
41+
res.status(500).json({ message: 'Error creating message', error: error.message });
42+
}
43+
};

controllers/reactionController.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const { Reaction } = require('../models');
2+
3+
exports.addReaction = async (req, res) => {
4+
try {
5+
const { messageId, emojiCode } = req.body;
6+
const reaction = await Reaction.create({
7+
message_id: messageId,
8+
user_id: req.userData.userId,
9+
emoji_code: emojiCode,
10+
});
11+
res.status(201).json(reaction);
12+
} catch (error) {
13+
res.status(500).json({ message: 'Error adding reaction', error: error.message });
14+
}
15+
};
16+
17+
exports.removeReaction = async (req, res) => {
18+
try {
19+
const { reactionId } = req.params;
20+
await Reaction.destroy({
21+
where: { reaction_id: reactionId, user_id: req.userData.userId },
22+
});
23+
res.json({ message: 'Reaction removed successfully' });
24+
} catch (error) {
25+
res.status(500).json({ message: 'Error removing reaction', error: error.message });
26+
}
27+
};

0 commit comments

Comments
 (0)