Compare commits

..

2 Commits

Author SHA1 Message Date
Kalista Payne
72539f9ba3 5.42.2 2025-12-10 14:16:53 -06:00
Kalista Payne
dabd466719 Revert "Chat optimization (#15545)"
This reverts commit 2917955ef0.
2025-12-10 14:16:48 -06:00
12 changed files with 20 additions and 189 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "habitica", "name": "habitica",
"version": "5.42.1", "version": "5.42.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "habitica", "name": "habitica",
"version": "5.42.1", "version": "5.42.2",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.22.10", "@babel/core": "^7.22.10",

View File

@@ -1,7 +1,7 @@
{ {
"name": "habitica", "name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.", "description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "5.42.1", "version": "5.42.2",
"main": "./website/server/index.js", "main": "./website/server/index.js",
"dependencies": { "dependencies": {
"@babel/core": "^7.22.10", "@babel/core": "^7.22.10",

View File

@@ -47,7 +47,7 @@ describe('GET /inbox/messages', () => {
it('returns four messages when using page-query ', async () => { it('returns four messages when using page-query ', async () => {
const promises = []; const promises = [];
for (let i = 0; i < 50; i += 1) { for (let i = 0; i < 10; i += 1) {
promises.push(user.post('/members/send-private-message', { promises.push(user.post('/members/send-private-message', {
toUserId: user.id, toUserId: user.id,
message: 'fourth', message: 'fourth',

View File

@@ -66,7 +66,7 @@ describe('GET /inbox/conversations', () => {
it('returns five messages when using page-query ', async () => { it('returns five messages when using page-query ', async () => {
const promises = []; const promises = [];
for (let i = 0; i < 50; i += 1) { for (let i = 0; i < 10; i += 1) {
promises.push(user.post('/members/send-private-message', { promises.push(user.post('/members/send-private-message', {
toUserId: user.id, toUserId: user.id,
message: 'fourth', message: 'fourth',

View File

@@ -396,32 +396,6 @@
class="btn btn-secondary" class="btn btn-secondary"
@click="makeAdmin()" @click="makeAdmin()"
>Make Admin</a> >Make Admin</a>
<div class="d-flex align-items-center mt-2">
<input
v-model.number="partyChatCount"
type="number"
min="1"
class="form-control form-control-sm mr-2"
style="width: 80px;"
>
<a
class="btn btn-secondary"
@click="seedPartyChat()"
>Send Party Chat Messages</a>
</div>
<div class="d-flex align-items-center mt-2">
<input
v-model.number="inboxCount"
type="number"
min="1"
class="form-control form-control-sm mr-2"
style="width: 80px;"
>
<a
class="btn btn-secondary"
@click="seedInbox()"
>Send Inbox Messages</a>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -912,8 +886,6 @@ export default {
DEBUG_ENABLED, DEBUG_ENABLED,
TIME_TRAVEL_ENABLED, TIME_TRAVEL_ENABLED,
lastTimeJump: null, lastTimeJump: null,
partyChatCount: 450,
inboxCount: 450,
}; };
}, },
computed: { computed: {
@@ -1032,32 +1004,6 @@ export default {
// Reload the website then go to Help > Admin Panel to set contributor level, etc.'); // Reload the website then go to Help > Admin Panel to set contributor level, etc.');
// @TODO: sync() // @TODO: sync()
}, },
async seedPartyChat () {
try {
const count = this.partyChatCount;
if (!Number.isInteger(count) || count < 1) {
window.alert('Please enter a positive integer'); // eslint-disable-line no-alert
return;
}
await axios.post('/api/v4/debug/seed-party-chat', { messageCount: count });
window.alert(`Successfully sent ${count} messages to your party chat!`); // eslint-disable-line no-alert
} catch (e) {
window.alert(e.response?.data?.message || 'Error sending party chat messages'); // eslint-disable-line no-alert
}
},
async seedInbox () {
try {
const count = this.inboxCount;
if (!Number.isInteger(count) || count < 1) {
window.alert('Please enter a positive integer'); // eslint-disable-line no-alert
return;
}
await axios.post('/api/v4/debug/seed-inbox', { messageCount: count });
window.alert(`Successfully sent ${count} messages to your inbox!`); // eslint-disable-line no-alert
} catch (e) {
window.alert(e.response?.data?.message || 'Error sending inbox messages'); // eslint-disable-line no-alert
}
},
donate () { donate () {
this.$root.$emit('bv::show::modal', 'buy-gems', { alreadyTracked: true }); this.$root.$emit('bv::show::modal', 'buy-gems', { alreadyTracked: true });
}, },

View File

@@ -679,7 +679,7 @@ import NotificationMixins from '@/mixins/notifications';
// extract to a shared path // extract to a shared path
const CONVERSATIONS_PER_PAGE = 10; const CONVERSATIONS_PER_PAGE = 10;
const PM_PER_PAGE = 50; const PM_PER_PAGE = 10;
const UI_STATES = Object.freeze({ const UI_STATES = Object.freeze({
LOADING: 'LOADING', LOADING: 'LOADING',

View File

@@ -3,7 +3,7 @@ import Vue from 'vue';
import * as Analytics from '@/libs/analytics'; import * as Analytics from '@/libs/analytics';
export async function getChat (store, payload) { export async function getChat (store, payload) {
const response = await axios.get(`/api/v4/groups/${payload.groupId}/chat?limit=400`); const response = await axios.get(`/api/v4/groups/${payload.groupId}/chat`);
return response.data.data; return response.data.data;
} }

View File

@@ -64,8 +64,6 @@ function textContainsBannedSlur (message) {
* *
* @apiParam (Path) {String} groupId The group _id ('party' for the user party and * @apiParam (Path) {String} groupId The group _id ('party' for the user party and
* 'habitrpg' for tavern are accepted). * 'habitrpg' for tavern are accepted).
* @apiParam (Query) {Number} [limit=50] The number of messages to fetch (max 400).
* @apiParam (Query) {String} [before] Fetch messages older than this message ID.
* *
* @apiSuccess {Array} data An array of <a href='https://github.com/HabitRPG/habitica/blob/develop/website/server/models/group.js#L51' target='_blank'>chat messages</a> * @apiSuccess {Array} data An array of <a href='https://github.com/HabitRPG/habitica/blob/develop/website/server/models/group.js#L51' target='_blank'>chat messages</a>
* *
@@ -80,21 +78,18 @@ api.getChat = {
const { user } = res.locals; const { user } = res.locals;
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty(); req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
req.checkQuery('before').optional().isUUID();
const validationErrors = req.validationErrors(); const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors; if (validationErrors) throw validationErrors;
const { groupId } = req.params; const { groupId } = req.params;
const limit = req.query.limit ? Math.min(parseInt(req.query.limit, 10), 400) : 50;
const { before } = req.query;
const group = await Group.getGroup({ user, groupId, fields: 'chat privacy' }); const group = await Group.getGroup({ user, groupId, fields: 'chat privacy' });
if (!group) throw new NotFound(res.t('groupNotFound')); if (!group) throw new NotFound(res.t('groupNotFound'));
if (group.privacy === 'public') { if (group.privacy === 'public') {
throw new BadRequest(res.t('featureRetired')); throw new BadRequest(res.t('featureRetired'));
} }
const groupChat = await Group.toJSONCleanChat(group, user, { limit, before }); const groupChat = await Group.toJSONCleanChat(group, user);
res.respond(200, groupChat.chat); res.respond(200, groupChat.chat);
}, },
}; };

View File

@@ -2,7 +2,6 @@ import mongoose from 'mongoose';
import get from 'lodash/get'; import get from 'lodash/get';
import sinon from 'sinon'; import sinon from 'sinon';
import moment from 'moment'; import moment from 'moment';
import { v4 as uuid } from 'uuid';
import { authWithHeaders } from '../../middlewares/auth'; import { authWithHeaders } from '../../middlewares/auth';
import ensureDevelopmentMode from '../../middlewares/ensureDevelopmentMode'; import ensureDevelopmentMode from '../../middlewares/ensureDevelopmentMode';
import ensureTimeTravelMode from '../../middlewares/ensureTimeTravelMode'; import ensureTimeTravelMode from '../../middlewares/ensureTimeTravelMode';
@@ -12,7 +11,6 @@ import {
model as Group, model as Group,
// basicFields as basicGroupFields, // basicFields as basicGroupFields,
} from '../../models/group'; } from '../../models/group';
import { chatModel as Chat, inboxModel as Inbox } from '../../models/message';
import connectToMongoDB from '../../libs/mongoose'; import connectToMongoDB from '../../libs/mongoose';
const { content } = common; const { content } = common;
@@ -313,93 +311,4 @@ api.timeTravelAdjust = {
}, },
}; };
api.seedPartyChat = {
method: 'POST',
url: '/debug/seed-party-chat',
middlewares: [ensureDevelopmentMode, authWithHeaders()],
async handler (req, res) {
const { user } = res.locals;
const messageCount = Number(req.body.messageCount);
if (!Number.isInteger(messageCount) || messageCount < 1) {
throw new BadRequest('messageCount must be a positive integer.');
}
if (!user.party._id) {
throw new BadRequest('You are not in a party.');
}
const party = await Group.findOne({ _id: user.party._id, type: 'party' }).exec();
if (!party) {
throw new BadRequest('Party not found.');
}
const messages = [];
const baseTimestamp = Date.now();
for (let i = 1; i <= messageCount; i += 1) {
const id = uuid();
messages.push({
_id: id,
id,
groupId: party._id,
text: `#${i}`,
unformattedText: `#${i}`,
timestamp: new Date(baseTimestamp - (messageCount - i) * 1000),
likes: {},
flags: {},
flagCount: 0,
uuid: 'system',
user: 'System',
client: 'debug-seed',
});
}
await Chat.insertMany(messages);
res.respond(200, { messageCount });
},
};
// Messaging ourselves for testing
api.seedInbox = {
method: 'POST',
url: '/debug/seed-inbox',
middlewares: [ensureDevelopmentMode, authWithHeaders()],
async handler (req, res) {
const { user } = res.locals;
const messageCount = Number(req.body.messageCount);
if (!Number.isInteger(messageCount) || messageCount < 1) {
throw new BadRequest('messageCount must be a positive integer.');
}
const messages = [];
const baseTimestamp = Date.now();
for (let i = 1; i <= messageCount; i += 1) {
const id = uuid();
messages.push({
_id: id,
id,
ownerId: user._id,
uuid: user._id,
user: user.profile.name,
text: `#${i}`,
unformattedText: `#${i}`,
timestamp: new Date(baseTimestamp - (messageCount - i) * 1000),
likes: {},
flags: {},
flagCount: 0,
sent: true,
client: 'debug-seed',
});
}
await Inbox.insertMany(messages);
res.respond(200, { messageCount });
},
};
export default api; export default api;

View File

@@ -9,9 +9,7 @@ import { // eslint-disable-line import/no-cycle
const questScrolls = shared.content.quests; const questScrolls = shared.content.quests;
// @TODO: Don't use this method when the group can be saved. // @TODO: Don't use this method when the group can be saved.
export async function getGroupChat (group, options = {}) { export async function getGroupChat (group) {
const { limit, before } = options;
let maxChatCount = MAX_CHAT_COUNT; let maxChatCount = MAX_CHAT_COUNT;
if (group.chatLimitCount && group.chatLimitCount >= MAX_CHAT_COUNT) { if (group.chatLimitCount && group.chatLimitCount >= MAX_CHAT_COUNT) {
maxChatCount = group.chatLimitCount; maxChatCount = group.chatLimitCount;
@@ -19,19 +17,10 @@ export async function getGroupChat (group, options = {}) {
maxChatCount = MAX_SUBBED_GROUP_CHAT_COUNT; maxChatCount = MAX_SUBBED_GROUP_CHAT_COUNT;
} }
const effectiveLimit = limit !== undefined ? Math.min(limit, maxChatCount) : maxChatCount; const groupChat = await Chat.find({ groupId: group._id })
.limit(maxChatCount)
let query = Chat.find({ groupId: group._id }) .sort('-timestamp')
.sort('-timestamp'); .exec();
if (before) {
const beforeMessage = await Chat.findOne({ _id: before }).exec();
if (beforeMessage) {
query = query.where('timestamp').lt(beforeMessage.timestamp);
}
}
const groupChat = await query.limit(effectiveLimit).exec();
// @TODO: Concat old chat to keep continuity of chat stored on group object // @TODO: Concat old chat to keep continuity of chat stored on group object
const currentGroupChat = group.chat || []; const currentGroupChat = group.chat || [];

View File

@@ -37,9 +37,7 @@ export async function sentMessage (sender, receiver, message, translate) {
return messageSent; return messageSent;
} }
// Paginate per every 50 const PM_PER_PAGE = 10;
const PM_PER_PAGE = 50;
const MAX_PM_COUNT = 400;
const getUserInboxDefaultOptions = { const getUserInboxDefaultOptions = {
asArray: true, asArray: true,
@@ -63,18 +61,12 @@ export async function getUserInbox (user, optionParams = getUserInboxDefaultOpti
.sort({ timestamp: -1 }); .sort({ timestamp: -1 });
if (typeof options.page !== 'undefined') { if (typeof options.page !== 'undefined') {
const page = Number(options.page);
const skip = PM_PER_PAGE * page;
if (skip >= MAX_PM_COUNT) {
return options.asArray ? [] : {};
}
const remainingAllowed = MAX_PM_COUNT - skip;
const limit = Math.min(PM_PER_PAGE, remainingAllowed);
query = query query = query
.skip(skip) .skip(PM_PER_PAGE * Number(options.page))
.limit(limit); .limit(PM_PER_PAGE);
} else { } else {
query = query.limit(MAX_PM_COUNT); // Limit for legacy calls that are not paginated to prevent database issues
query = query.limit(200);
} }
const messages = (await query.lean().exec()).map(msgObj => { const messages = (await query.lean().exec()).map(msgObj => {

View File

@@ -345,12 +345,12 @@ schema.statics.getGroups = async function getGroups (options = {}) {
// unless the user is an admin or said chat is posted by that user // unless the user is an admin or said chat is posted by that user
// Not putting into toJSON because there we can't access user // Not putting into toJSON because there we can't access user
// It also removes the _meta field that can be stored inside a chat message // It also removes the _meta field that can be stored inside a chat message
schema.statics.toJSONCleanChat = async function groupToJSONCleanChat (group, user, options = {}) { schema.statics.toJSONCleanChat = async function groupToJSONCleanChat (group, user) {
// @TODO: Adding this here for support the old chat, // @TODO: Adding this here for support the old chat,
// but we should depreciate accessing chat like this // but we should depreciate accessing chat like this
// Also only return chat if requested, eventually we don't want to return chat here // Also only return chat if requested, eventually we don't want to return chat here
if (group && group.chat) { if (group && group.chat) {
await getGroupChat(group, options); await getGroupChat(group);
} }
const groupToJson = group.toJSON(); const groupToJson = group.toJSON();