simplified message model

This commit is contained in:
jackiettran
2025-11-25 17:22:57 -05:00
parent 2983f67ce8
commit 31d94b1b3f
12 changed files with 74 additions and 473 deletions

View File

@@ -0,0 +1,61 @@
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Messages", {
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
primaryKey: true,
},
senderId: {
type: Sequelize.UUID,
allowNull: false,
references: {
model: "Users",
key: "id",
},
onUpdate: "CASCADE",
onDelete: "CASCADE",
},
receiverId: {
type: Sequelize.UUID,
allowNull: false,
references: {
model: "Users",
key: "id",
},
onUpdate: "CASCADE",
onDelete: "CASCADE",
},
content: {
type: Sequelize.TEXT,
allowNull: false,
},
isRead: {
type: Sequelize.BOOLEAN,
defaultValue: false,
},
imagePath: {
type: Sequelize.STRING,
allowNull: true,
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
},
});
// Add indexes
await queryInterface.addIndex("Messages", ["senderId"]);
await queryInterface.addIndex("Messages", ["receiverId"]);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Messages");
},
};

View File

@@ -23,10 +23,6 @@ const Message = sequelize.define('Message', {
key: 'id'
}
},
subject: {
type: DataTypes.STRING,
allowNull: false
},
content: {
type: DataTypes.TEXT,
allowNull: false
@@ -35,14 +31,6 @@ const Message = sequelize.define('Message', {
type: DataTypes.BOOLEAN,
defaultValue: false
},
parentMessageId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'Messages',
key: 'id'
}
},
imagePath: {
type: DataTypes.STRING,
allowNull: true

View File

@@ -27,11 +27,6 @@ User.hasMany(Message, { as: "sentMessages", foreignKey: "senderId" });
User.hasMany(Message, { as: "receivedMessages", foreignKey: "receiverId" });
Message.belongsTo(User, { as: "sender", foreignKey: "senderId" });
Message.belongsTo(User, { as: "receiver", foreignKey: "receiverId" });
Message.hasMany(Message, { as: "replies", foreignKey: "parentMessageId" });
Message.belongsTo(Message, {
as: "parentMessage",
foreignKey: "parentMessageId",
});
// Forum associations
User.hasMany(ForumPost, { as: "forumPosts", foreignKey: "authorId" });

View File

@@ -171,11 +171,11 @@ router.get('/sent', authenticateToken, async (req, res) => {
}
});
// Get a single message with replies
// Get a single message
router.get('/:id', authenticateToken, async (req, res) => {
try {
const message = await Message.findOne({
where: {
where: {
id: req.params.id,
[require('sequelize').Op.or]: [
{ senderId: req.user.id },
@@ -192,15 +192,6 @@ router.get('/:id', authenticateToken, async (req, res) => {
model: User,
as: 'receiver',
attributes: ['id', 'firstName', 'lastName', 'profileImage']
},
{
model: Message,
as: 'replies',
include: [{
model: User,
as: 'sender',
attributes: ['id', 'firstName', 'lastName', 'profileImage']
}]
}
]
});
@@ -248,7 +239,7 @@ router.get('/:id', authenticateToken, async (req, res) => {
// Send a new message
router.post('/', authenticateToken, uploadMessageImage, async (req, res) => {
try {
const { receiverId, subject, content, parentMessageId } = req.body;
const { receiverId, content } = req.body;
// Check if receiver exists
const receiver = await User.findByPk(receiverId);
@@ -267,9 +258,7 @@ router.post('/', authenticateToken, uploadMessageImage, async (req, res) => {
const message = await Message.create({
senderId: req.user.id,
receiverId,
subject,
content,
parentMessageId,
imagePath
});
@@ -308,8 +297,7 @@ router.post('/', authenticateToken, uploadMessageImage, async (req, res) => {
reqLogger.info("Message sent", {
senderId: req.user.id,
receiverId: receiverId,
messageId: message.id,
isReply: !!parentMessageId
messageId: message.id
});
res.status(201).json(messageWithSender);

View File

@@ -39,7 +39,6 @@ class MessagingEmailService {
* @param {string} sender.firstName - Sender's first name
* @param {string} sender.lastName - Sender's last name
* @param {Object} message - Message object
* @param {string} message.subject - Message subject
* @param {string} message.content - Message content
* @param {Date} message.createdAt - Message creation timestamp
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
@@ -61,7 +60,6 @@ class MessagingEmailService {
const variables = {
recipientName: receiver.firstName || "there",
senderName: `${sender.firstName} ${sender.lastName}`.trim() || "A user",
subject: message.subject,
messageContent: message.content,
conversationUrl: conversationUrl,
timestamp: timestamp,

View File

@@ -134,13 +134,6 @@
border-radius: 6px;
}
.message-box .subject {
font-size: 16px;
font-weight: 600;
color: #495057;
margin: 0 0 15px 0;
}
.message-box .content-text {
color: #212529;
line-height: 1.6;
@@ -226,7 +219,6 @@
<p>{{senderName}} sent you a message on RentAll.</p>
<div class="message-box">
<div class="subject">Subject: {{subject}}</div>
<div class="content-text">{{messageContent}}</div>
<div class="timestamp">Sent {{timestamp}}</div>
</div>

View File

@@ -56,7 +56,6 @@ describe('Messages Routes', () => {
id: 1,
senderId: 2,
receiverId: 1,
subject: 'Test Message',
content: 'Hello there!',
isRead: false,
createdAt: '2024-01-15T10:00:00.000Z',
@@ -71,7 +70,6 @@ describe('Messages Routes', () => {
id: 2,
senderId: 3,
receiverId: 1,
subject: 'Another Message',
content: 'Hi!',
isRead: true,
createdAt: '2024-01-14T10:00:00.000Z',
@@ -122,7 +120,6 @@ describe('Messages Routes', () => {
id: 3,
senderId: 1,
receiverId: 2,
subject: 'My Message',
content: 'Hello Jane!',
isRead: false,
createdAt: '2024-01-15T12:00:00.000Z',
@@ -171,7 +168,6 @@ describe('Messages Routes', () => {
id: 1,
senderId: 2,
receiverId: 1,
subject: 'Test Message',
content: 'Hello there!',
isRead: false,
createdAt: '2024-01-15T10:00:00.000Z',
@@ -187,19 +183,6 @@ describe('Messages Routes', () => {
lastName: 'Doe',
profileImage: 'john.jpg'
},
replies: [
{
id: 4,
senderId: 1,
content: 'Reply message',
sender: {
id: 1,
firstName: 'John',
lastName: 'Doe',
profileImage: 'john.jpg'
}
}
],
update: jest.fn()
};
@@ -207,7 +190,7 @@ describe('Messages Routes', () => {
mockMessageFindOne.mockResolvedValue(mockMessage);
});
it('should get message with replies for receiver', async () => {
it('should get message for receiver and mark as read', async () => {
mockMessage.update.mockResolvedValue();
const response = await request(app)
@@ -218,7 +201,6 @@ describe('Messages Routes', () => {
id: 1,
senderId: 2,
receiverId: 1,
subject: 'Test Message',
content: 'Hello there!',
isRead: false,
createdAt: '2024-01-15T10:00:00.000Z',
@@ -233,20 +215,7 @@ describe('Messages Routes', () => {
firstName: 'John',
lastName: 'Doe',
profileImage: 'john.jpg'
},
replies: [
{
id: 4,
senderId: 1,
content: 'Reply message',
sender: {
id: 1,
firstName: 'John',
lastName: 'Doe',
profileImage: 'john.jpg'
}
}
]
}
});
expect(mockMessage.update).toHaveBeenCalledWith({ isRead: true });
});
@@ -263,7 +232,6 @@ describe('Messages Routes', () => {
id: 1,
senderId: 1,
receiverId: 2,
subject: 'Test Message',
content: 'Hello there!',
isRead: false,
createdAt: '2024-01-15T10:00:00.000Z',
@@ -278,20 +246,7 @@ describe('Messages Routes', () => {
firstName: 'John',
lastName: 'Doe',
profileImage: 'john.jpg'
},
replies: [
{
id: 4,
senderId: 1,
content: 'Reply message',
sender: {
id: 1,
firstName: 'John',
lastName: 'Doe',
profileImage: 'john.jpg'
}
}
]
}
});
expect(mockMessage.update).not.toHaveBeenCalled();
});
@@ -340,9 +295,7 @@ describe('Messages Routes', () => {
id: 5,
senderId: 1,
receiverId: 2,
subject: 'New Message',
content: 'Hello Jane!',
parentMessageId: null
content: 'Hello Jane!'
};
const mockMessageWithSender = {
@@ -364,9 +317,7 @@ describe('Messages Routes', () => {
it('should create a new message', async () => {
const messageData = {
receiverId: 2,
subject: 'New Message',
content: 'Hello Jane!',
parentMessageId: null
content: 'Hello Jane!'
};
const response = await request(app)
@@ -378,31 +329,8 @@ describe('Messages Routes', () => {
expect(mockMessageCreate).toHaveBeenCalledWith({
senderId: 1,
receiverId: 2,
subject: 'New Message',
content: 'Hello Jane!',
parentMessageId: null
});
});
it('should create a reply message with parentMessageId', async () => {
const replyData = {
receiverId: 2,
subject: 'Re: Original Message',
content: 'This is a reply',
parentMessageId: 1
};
const response = await request(app)
.post('/messages')
.send(replyData);
expect(response.status).toBe(201);
expect(mockMessageCreate).toHaveBeenCalledWith({
senderId: 1,
receiverId: 2,
subject: 'Re: Original Message',
content: 'This is a reply',
parentMessageId: 1
imagePath: null
});
});
@@ -413,7 +341,6 @@ describe('Messages Routes', () => {
.post('/messages')
.send({
receiverId: 999,
subject: 'Test',
content: 'Test message'
});
@@ -426,7 +353,6 @@ describe('Messages Routes', () => {
.post('/messages')
.send({
receiverId: 1, // Same as sender ID
subject: 'Self Message',
content: 'Hello self!'
});
@@ -441,7 +367,6 @@ describe('Messages Routes', () => {
.post('/messages')
.send({
receiverId: 2,
subject: 'Test',
content: 'Test message'
});
@@ -596,62 +521,5 @@ describe('Messages Routes', () => {
expect(response.status).toBe(200);
expect(response.body).toEqual([]);
});
it('should handle message with no replies', async () => {
const messageWithoutReplies = {
id: 1,
senderId: 2,
receiverId: 1,
subject: 'Test Message',
content: 'Hello there!',
isRead: false,
replies: [],
update: jest.fn()
};
mockMessageFindOne.mockResolvedValue(messageWithoutReplies);
const response = await request(app)
.get('/messages/1');
expect(response.status).toBe(200);
expect(response.body.replies).toEqual([]);
});
it('should handle missing optional fields in message creation', async () => {
const mockReceiver = { id: 2, firstName: 'Jane', lastName: 'Smith' };
const mockCreatedMessage = {
id: 6,
senderId: 1,
receiverId: 2,
subject: undefined,
content: 'Just content',
parentMessageId: undefined
};
const mockMessageWithSender = {
...mockCreatedMessage,
sender: { id: 1, firstName: 'John', lastName: 'Doe' }
};
mockUserFindByPk.mockResolvedValue(mockReceiver);
mockMessageCreate.mockResolvedValue(mockCreatedMessage);
mockMessageFindByPk.mockResolvedValue(mockMessageWithSender);
const response = await request(app)
.post('/messages')
.send({
receiverId: 2,
content: 'Just content'
// subject and parentMessageId omitted
});
expect(response.status).toBe(201);
expect(mockMessageCreate).toHaveBeenCalledWith({
senderId: 1,
receiverId: 2,
subject: undefined,
content: 'Just content',
parentMessageId: undefined
});
});
});
});