mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +01:00
fix invalid push devices
This commit is contained in:
19
test/api/unit/models/pushDevice.test.js
Normal file
19
test/api/unit/models/pushDevice.test.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { model as PushDevice } from '../../../../website/server/models/pushDevice';
|
||||||
|
|
||||||
|
describe('PushDevice Model', () => {
|
||||||
|
context('cleanupCorruptData', () => {
|
||||||
|
it('converts an array of push devices to a safe version', () => {
|
||||||
|
const pushDevices = [
|
||||||
|
null, // invalid, not an object
|
||||||
|
{ regId: '123' }, // invalid, no type
|
||||||
|
{ type: 'android' }, // invalid, no regId
|
||||||
|
new PushDevice({ type: 'android', regId: '1234' }), // valid
|
||||||
|
];
|
||||||
|
|
||||||
|
const safePushDevices = PushDevice.cleanupCorruptData(pushDevices);
|
||||||
|
expect(safePushDevices.length).to.equal(1);
|
||||||
|
expect(safePushDevices[0].type).to.equal('android');
|
||||||
|
expect(safePushDevices[0].regId).to.equal('1234');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -181,18 +181,29 @@ describe('User Model', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('notifications', () => {
|
context('post init', () => {
|
||||||
it('can add notifications without data', () => {
|
it('removes invalid push devices when loading the user', async () => {
|
||||||
const user = new User();
|
let user = new User();
|
||||||
|
await user.save();
|
||||||
|
await user.update({
|
||||||
|
$set: {
|
||||||
|
pushDevices: [
|
||||||
|
null, // invalid, not an object
|
||||||
|
{ regId: '123' }, // invalid, no type
|
||||||
|
{ type: 'android' }, // invalid, no regId
|
||||||
|
{ type: 'android', regId: '1234' }, // valid
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}).exec();
|
||||||
|
|
||||||
user.addNotification('CRON');
|
user = await User.findById(user._id).exec();
|
||||||
|
|
||||||
const userToJSON = user.toJSON();
|
const userToJSON = user.toJSON();
|
||||||
expect(user.notifications.length).to.equal(1);
|
expect(userToJSON.pushDevices.length).to.equal(1);
|
||||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
|
||||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
expect(userToJSON.pushDevices[0]).to.have.all.keys(['regId', 'type', 'createdAt', 'updatedAt']);
|
||||||
expect(userToJSON.notifications[0].data).to.eql({});
|
expect(userToJSON.pushDevices[0].type).to.equal('android');
|
||||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
expect(userToJSON.pushDevices[0].regId).to.equal('1234');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('removes invalid notifications when loading the user', async () => {
|
it('removes invalid notifications when loading the user', async () => {
|
||||||
@@ -220,6 +231,21 @@ describe('User Model', () => {
|
|||||||
expect(userToJSON.notifications[0].type).to.equal('ABC');
|
expect(userToJSON.notifications[0].type).to.equal('ABC');
|
||||||
expect(userToJSON.notifications[0].id).to.equal('123');
|
expect(userToJSON.notifications[0].id).to.equal('123');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('notifications', () => {
|
||||||
|
it('can add notifications without data', () => {
|
||||||
|
const user = new User();
|
||||||
|
|
||||||
|
user.addNotification('CRON');
|
||||||
|
|
||||||
|
const userToJSON = user.toJSON();
|
||||||
|
expect(user.notifications.length).to.equal(1);
|
||||||
|
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||||
|
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||||
|
expect(userToJSON.notifications[0].data).to.eql({});
|
||||||
|
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('can add notifications with data and already marked as seen', () => {
|
it('can add notifications with data and already marked as seen', () => {
|
||||||
const user = new User();
|
const user = new User();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import _ from 'lodash';
|
||||||
import baseModel from '../libs/baseModel';
|
import baseModel from '../libs/baseModel';
|
||||||
|
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
@@ -19,4 +20,30 @@ schema.plugin(baseModel, {
|
|||||||
_id: false,
|
_id: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove invalid data from an array of push devices.
|
||||||
|
* Fix for https://github.com/HabitRPG/habitica/issues/11805
|
||||||
|
* and https://github.com/HabitRPG/habitica/issues/11868
|
||||||
|
* Called by user's post init hook (models/user/hooks.js)
|
||||||
|
*/
|
||||||
|
schema.statics.cleanupCorruptData = function cleanupCorruptPushDevicesData (pushDevices) {
|
||||||
|
if (!pushDevices) return pushDevices;
|
||||||
|
|
||||||
|
let filteredPushDevices = pushDevices.filter(pushDevice => {
|
||||||
|
// Exclude push devices with a nullish value, no id or no type
|
||||||
|
if (!pushDevice || !pushDevice.regId || !pushDevice.type) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove duplicate push devices
|
||||||
|
// can be caused by a race condition when adding a new push device
|
||||||
|
filteredPushDevices = _.uniqWith(filteredPushDevices, (val, otherVal) => {
|
||||||
|
if (val.regId === otherVal.regId && val.type === otherVal.type) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filteredPushDevices;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const model = mongoose.model('PushDevice', schema);
|
export const model = mongoose.model('PushDevice', schema);
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import * as Tasks from '../task';
|
|||||||
import {
|
import {
|
||||||
model as UserNotification,
|
model as UserNotification,
|
||||||
} from '../userNotification';
|
} from '../userNotification';
|
||||||
|
import {
|
||||||
|
model as PushDevice,
|
||||||
|
} from '../pushDevice';
|
||||||
import { // eslint-disable-line import/no-cycle
|
import { // eslint-disable-line import/no-cycle
|
||||||
userActivityWebhook,
|
userActivityWebhook,
|
||||||
} from '../../libs/webhook';
|
} from '../../libs/webhook';
|
||||||
@@ -190,6 +193,11 @@ schema.post('init', function postInitUser () {
|
|||||||
this.notifications = UserNotification.cleanupCorruptData(this.notifications);
|
this.notifications = UserNotification.cleanupCorruptData(this.notifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure pushDevices are loaded
|
||||||
|
if (this.isDirectSelected('pushDevices')) {
|
||||||
|
this.pushDevices = PushDevice.cleanupCorruptData(this.pushDevices);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ export const schema = new Schema({
|
|||||||
* Called by user's post init hook (models/user/hooks.js)
|
* Called by user's post init hook (models/user/hooks.js)
|
||||||
*/
|
*/
|
||||||
schema.statics.cleanupCorruptData = function cleanupCorruptNotificationsData (notifications) {
|
schema.statics.cleanupCorruptData = function cleanupCorruptNotificationsData (notifications) {
|
||||||
console.log('fixing stuff', notifications);
|
|
||||||
if (!notifications) return notifications;
|
if (!notifications) return notifications;
|
||||||
|
|
||||||
let filteredNotifications = notifications.filter(notification => {
|
let filteredNotifications = notifications.filter(notification => {
|
||||||
|
|||||||
Reference in New Issue
Block a user