diff --git a/test/api/unit/models/group.test.js b/test/api/unit/models/group.test.js index e2d54d8575..25083c2583 100644 --- a/test/api/unit/models/group.test.js +++ b/test/api/unit/models/group.test.js @@ -6,6 +6,8 @@ import { SPAM_MESSAGE_LIMIT, SPAM_MIN_EXEMPT_CONTRIB_LEVEL, SPAM_WINDOW_LENGTH, + MAX_CHAT_COUNT, + MAX_SUBBED_GROUP_CHAT_COUNT, INVITES_LIMIT, model as Group, } from '../../../../website/server/models/group'; @@ -18,6 +20,7 @@ import { import * as email from '../../../../website/server/libs/email'; import { TAVERN_ID } from '../../../../website/common/script/constants'; import shared from '../../../../website/common'; +import { chatModel as Chat } from '../../../../website/server/models/message'; describe('Group Model', () => { let party; let questLeader; let participatingMember; @@ -1356,6 +1359,29 @@ describe('Group Model', () => { }); }); + describe('#getEffectiveChatLimit', () => { + it('returns the correct chat limit', () => { + const group = new Group(); + expect(group.getEffectiveChatLimit()).to.eql(MAX_CHAT_COUNT); + }); + + it('returns the passed limit if it is lower than the max', () => { + const group = new Group(); + expect(group.getEffectiveChatLimit(10)).to.eql(10); + }); + + it('returns the max if the passed limit is higher', () => { + const group = new Group(); + expect(group.getEffectiveChatLimit(MAX_CHAT_COUNT + 10)).to.eql(MAX_CHAT_COUNT); + }); + + it('returns the max for group plans', () => { + const group = new Group(); + group.purchased.plan.customerId = '110002222333'; + expect(group.getEffectiveChatLimit()).to.eql(MAX_SUBBED_GROUP_CHAT_COUNT); + }); + }); + describe('#sendChat', () => { beforeEach(() => { sandbox.spy(User, 'updateOne'); @@ -1462,6 +1488,34 @@ describe('Group Model', () => { }); }); + describe('#trimChat', () => { + it('Only checks last message when not enough messages to trim', async () => { + sandbox.spy(Chat, 'find'); + sandbox.spy(Chat, 'deleteMany'); + await Chat.insertOne({ groupId: party._id, timestamp: new Date() }); + await Chat.insertOne({ groupId: party._id, timestamp: new Date() }); + await Chat.insertOne({ groupId: party._id, timestamp: new Date() }); + await party.trimChat(); + + expect(Chat.find).to.be.calledOnce; + expect(Chat.deleteMany).to.not.be.called; + expect(await Chat.countDocuments({ groupId: party._id })).to.eql(3); + }); + it('Deletes messages over the limit', async () => { + sandbox.spy(Chat, 'find'); + sandbox.spy(Chat, 'deleteMany'); + await Chat.insertOne({ groupId: party._id, timestamp: new Date() }); + await Chat.insertOne({ groupId: party._id, timestamp: new Date() }); + await Chat.insertOne({ groupId: party._id, timestamp: new Date() }); + + await party.trimChat(1); + + expect(Chat.find).to.be.calledOnce; + expect(Chat.deleteMany).to.be.calledOnce; + expect(await Chat.countDocuments({ groupId: party._id })).to.eql(1); + }); + }); + describe('#startQuest', () => { context('Failure Conditions', () => { it('throws an error if group is not a party', async () => { diff --git a/website/server/controllers/api-v3/chat.js b/website/server/controllers/api-v3/chat.js index 40ed380031..c50ae20366 100644 --- a/website/server/controllers/api-v3/chat.js +++ b/website/server/controllers/api-v3/chat.js @@ -227,6 +227,7 @@ api.postChat = { } await Promise.all(toSave); + await group.trimChat(); if (chatUpdated) { res.respond(200, { chat: chatRes.chat }); diff --git a/website/server/libs/chat/group-chat.js b/website/server/libs/chat/group-chat.js index 028f987b4a..1e0ca50fc0 100644 --- a/website/server/libs/chat/group-chat.js +++ b/website/server/libs/chat/group-chat.js @@ -1,25 +1,13 @@ import _ from 'lodash'; import { chatModel as Chat } from '../../models/message'; import shared from '../../../common'; -import { // eslint-disable-line import/no-cycle - MAX_CHAT_COUNT, - MAX_SUBBED_GROUP_CHAT_COUNT, -} from '../../models/group'; const questScrolls = shared.content.quests; // @TODO: Don't use this method when the group can be saved. export async function getGroupChat (group, options = {}) { const { limit, before } = options; - - let maxChatCount = MAX_CHAT_COUNT; - if (group.chatLimitCount && group.chatLimitCount >= MAX_CHAT_COUNT) { - maxChatCount = group.chatLimitCount; - } else if (group.hasActiveGroupPlan()) { - maxChatCount = MAX_SUBBED_GROUP_CHAT_COUNT; - } - - const effectiveLimit = limit !== undefined ? Math.min(limit, maxChatCount) : maxChatCount; + const effectiveLimit = group.getEffectiveChatLimit(limit); let query = Chat.find({ groupId: group._id }) .sort('-timestamp'); diff --git a/website/server/models/group.js b/website/server/models/group.js index 2517d01162..845584b13b 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -497,6 +497,17 @@ schema.statics.validateInvitations = async function getInvitationErr (invites, r } }; +schema.methods.getEffectiveChatLimit = function getEffectiveChatLimit (limit) { + let maxChatCount = MAX_CHAT_COUNT; + if (this.chatLimitCount && this.chatLimitCount >= MAX_CHAT_COUNT) { + maxChatCount = this.chatLimitCount; + } else if (this.hasActiveGroupPlan()) { + maxChatCount = MAX_SUBBED_GROUP_CHAT_COUNT; + } + + return limit !== undefined ? Math.min(limit, maxChatCount) : maxChatCount; +}; + schema.methods.getParticipatingQuestMembers = function getParticipatingQuestMembers () { return Object.keys(this.quest.members).filter(member => this.quest.members[member]); }; @@ -654,6 +665,18 @@ schema.methods.sendChat = async function sendChat (options = {}) { return newChatMessage; }; +schema.methods.trimChat = async function trimChat (limit) { + const query = Chat.find({ groupId: this._id }) + .sort('-timestamp') + .skip(limit || (this.getEffectiveChatLimit() * 2)) + .limit(1); + const lastMessage = await query.exec(); + if (lastMessage && lastMessage.length > 0) { + const lastMessageTimestamp = lastMessage[0].timestamp; + await Chat.deleteMany({ groupId: this._id, timestamp: { $lte: lastMessageTimestamp } }).exec(); + } +}; + // eslint-disable-next-line max-len schema.methods.handleQuestInvitation = async function handleQuestInvitation (user, accept, session) { if (!user) throw new InternalServerError('Must provide user to handle quest invitation');