Skip to content

Commit

Permalink
Move inbox to its own model (#10428)
Browse files Browse the repository at this point in the history
* shared model for chat and inbox

* disable inbox schema

* inbox: use separate model

* remove old code that used group.chat

* add back chat field (not used) and remove old tests

* remove inbox exclusions when loading user

* add GET /api/v3/inbox/messages

* add comment

* implement DELETE /inbox/messages/:messageid in v4

* implement GET /inbox/messages in v4 and update tests

* implement DELETE /api/v4/inbox/clear

* fix url

* fix doc

* update /export/inbox.html

* update other data exports

* add back messages in user schema

* add user.toJSONWithInbox

* add compativility until migration is done

* more compatibility

* fix tojson called twice

* add compatibility methods

* fix common tests

* fix v4 integration tests

* v3 get user -> with inbox

* start to fix tests

* fix v3 integration tests

* wip

* wip, client use new route

* update tests for members/send-private-message

* tests for get user in v4

* add tests for DELETE /inbox/messages/:messageId

* add tests for DELETE /inbox/clear in v4

* update docs

* fix tests

* initial migration

* fix migration

* fix migration

* migration fixes

* migrate api.enterCouponCode

* migrate api.castSpell

* migrate reset, reroll, rebirth

* add routes to v4 version

* fix tests

* fixes

* api.updateUser

* remove .only

* get user -> userLib

* refactor inbox.vue to work with new data model

* fix return message when messaging yourself

* wip fix bug with new conversation

* wip

* fix remaining ui issues

* move api.registerLocal, fixes

* keep only v3 version of GET /inbox/messages
  • Loading branch information
paglias committed Sep 21, 2018
1 parent bb7d447 commit 26c8323
Show file tree
Hide file tree
Showing 61 changed files with 3,173 additions and 1,094 deletions.
123 changes: 123 additions & 0 deletions migrations/20180811_inboxOutsideUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const migrationName = '20180811_inboxOutsideUser.js';
const authorName = 'paglias'; // in case script author needs to know when their ...
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done

/*
* Move inbox messages from the user model to their own collection
*/

const monk = require('monk');
const nconf = require('nconf');

const Inbox = require('../website/server/models/message').inboxModel;
const connectionString = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
const dbInboxes = monk(connectionString).get('inboxes', { castIds: false });
const dbUsers = monk(connectionString).get('users', { castIds: false });

function processUsers (lastId) {
let query = {
migration: {$ne: migrationName},
};

if (lastId) {
query._id = {
$gt: lastId,
};
}

dbUsers.find(query, {
sort: {_id: 1},
limit: 1000,
fields: ['_id', 'inbox'],
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}

let progressCount = 1000;
let count = 0;
let msgCount = 0;

function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users and their tasks found and modified.');
displayData();
return;
}

let usersPromises = users.map(updateUser);
let lastUser = users[users.length - 1];

return Promise.all(usersPromises)
.then(() => {
return processUsers(lastUser._id);
});
}

function updateUser (user) {
count++;

if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
if (msgCount % progressCount === 0) console.warn(`${msgCount } messages processed`);
if (user._id === authorUuid) console.warn(`${authorName } being processed`);

const oldInboxMessages = user.inbox.messages || {};
const oldInboxMessagesIds = Object.keys(oldInboxMessages);

msgCount += oldInboxMessagesIds.length;

const newInboxMessages = oldInboxMessagesIds.map(msgId => {
const msg = oldInboxMessages[msgId];
if (!msg || (!msg.id && !msg._id)) { // eslint-disable-line no-extra-parens
console.log('missing message or message _id and id', msg);
throw new Error('error!');
}

if (msg.id && !msg._id) msg._id = msg.id;
if (msg._id && !msg.id) msg.id = msg._id;

const newMsg = new Inbox(msg);
newMsg.ownerId = user._id;
return newMsg.toJSON();
});

return dbInboxes.insert(newInboxMessages)
.then(() => {
return dbUsers.update({_id: user._id}, {
$set: {
migration: migrationName,
'inbox.messages': {},
},
});
})
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}

function displayData () {
console.warn(`\n${ count } users processed\n`);
console.warn(`\n${ msgCount } messages processed\n`);
return exiting(0);
}

function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
}
}
process.exit(code);
}

module.exports = processUsers;
26 changes: 0 additions & 26 deletions test/api/unit/models/group.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1020,32 +1020,6 @@ describe('Group Model', () => {
expect(chat.user).to.not.exist;
});

it('cuts down chat to 200 messages', () => {
for (let i = 0; i < 220; i++) {
party.chat.push({ text: 'a message' });
}

expect(party.chat).to.have.a.lengthOf(220);

party.sendChat('message');

expect(party.chat).to.have.a.lengthOf(200);
});

it('cuts down chat to 400 messages when group is subcribed', () => {
party.purchased.plan.customerId = 'test-customer-id';

for (let i = 0; i < 420; i++) {
party.chat.push({ text: 'a message' });
}

expect(party.chat).to.have.a.lengthOf(420);

party.sendChat('message');

expect(party.chat).to.have.a.lengthOf(400);
});

it('updates users about new messages in party', () => {
party.sendChat('message');

Expand Down
1 change: 1 addition & 0 deletions test/api/v3/integration/inbox/GET-inbox_messages.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('GET /inbox/messages', () => {

// message to yourself
expect(messages[0].text).to.equal('fourth');
expect(messages[0].sent).to.equal(false);
expect(messages[0].uuid).to.equal(user._id);

expect(messages[1].text).to.equal('third');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
generateUser,
sleep,
} from '../../../../helpers/api-integration/v3';
import { model as Chat } from '../../../../../website/server/models/chat';
import { chatModel as Chat } from '../../../../../website/server/models/message';

describe('POST /groups/:groupId/quests/accept', () => {
const PET_QUEST = 'whale';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
generateUser,
sleep,
} from '../../../../helpers/api-integration/v3';
import { model as Chat } from '../../../../../website/server/models/chat';
import { chatModel as Chat } from '../../../../../website/server/models/message';

describe('POST /groups/:groupId/quests/force-start', () => {
const PET_QUEST = 'whale';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from '../../../../helpers/api-integration/v3';
import { v4 as generateUUID } from 'uuid';
import { quests as questScrolls } from '../../../../../website/common/script/content';
import { model as Chat } from '../../../../../website/server/models/chat';
import { chatModel as Chat } from '../../../../../website/server/models/message';
import apiError from '../../../../../website/server/libs/apiError';

describe('POST /groups/:groupId/quests/invite/:questKey', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
sleep,
} from '../../../../helpers/api-integration/v3';
import { v4 as generateUUID } from 'uuid';
import { model as Chat } from '../../../../../website/server/models/chat';
import { chatModel as Chat } from '../../../../../website/server/models/message';

describe('POST /groups/:groupId/quests/reject', () => {
let questingGroup;
Expand Down
62 changes: 62 additions & 0 deletions test/api/v4/coupon/POST-coupons_enter_code.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
generateUser,
translate as t,
resetHabiticaDB,
} from '../../../helpers/api-integration/v4';

describe('POST /coupons/enter/:code', () => {
let user;
let sudoUser;

before(async () => {
await resetHabiticaDB();
});

beforeEach(async () => {
user = await generateUser();
sudoUser = await generateUser({
'contributor.sudo': true,
});
});

it('returns an error if code is missing', async () => {
await expect(user.post('/coupons/enter')).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: 'Not found.',
});
});

it('returns an error if code is invalid', async () => {
await expect(user.post('/coupons/enter/notValid')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidCoupon'),
});
});

it('returns an error if coupon has been used', async () => {
let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
await user.post(`/coupons/enter/${coupon._id}`); // use coupon

await expect(user.post(`/coupons/enter/${coupon._id}`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('couponUsed'),
});
});

it('should apply the coupon to the user', async () => {
let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
let userRes = await user.post(`/coupons/enter/${coupon._id}`);
expect(userRes._id).to.equal(user._id);
expect(userRes.items.gear.owned.eyewear_special_wondercon_red).to.be.true;
expect(userRes.items.gear.owned.eyewear_special_wondercon_black).to.be.true;
expect(userRes.items.gear.owned.back_special_wondercon_black).to.be.true;
expect(userRes.items.gear.owned.back_special_wondercon_red).to.be.true;
expect(userRes.items.gear.owned.body_special_wondercon_red).to.be.true;
expect(userRes.items.gear.owned.body_special_wondercon_black).to.be.true;
expect(userRes.items.gear.owned.body_special_wondercon_gold).to.be.true;
expect(userRes.extra).to.eql({signupEvent: 'wondercon'});
});
});
30 changes: 30 additions & 0 deletions test/api/v4/inbox/DELETE-inbox_clear.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
generateUser,
} from '../../../helpers/api-integration/v4';

describe('DELETE /inbox/clear', () => {
it('removes all inbox messages for the user', async () => {
const [user, otherUser] = await Promise.all([generateUser(), generateUser()]);

await otherUser.post('/members/send-private-message', {
toUserId: user.id,
message: 'first',
});
await user.post('/members/send-private-message', {
toUserId: otherUser.id,
message: 'second',
});
await otherUser.post('/members/send-private-message', {
toUserId: user.id,
message: 'third',
});

let messages = await user.get('/inbox/messages');
expect(messages.length).to.equal(3);

await user.del('/inbox/clear/');

messages = await user.get('/inbox/messages');
expect(messages.length).to.equal(0);
});
});
58 changes: 58 additions & 0 deletions test/api/v4/user/GET-user.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
generateUser,
} from '../../../helpers/api-integration/v4';
import common from '../../../../website/common';

describe('GET /user', () => {
let user;

before(async () => {
user = await generateUser();
});

it('returns the authenticated user with computed stats', async () => {
let returnedUser = await user.get('/user');
expect(returnedUser._id).to.equal(user._id);

expect(returnedUser.stats.maxMP).to.exist;
expect(returnedUser.stats.maxHealth).to.equal(common.maxHealth);
expect(returnedUser.stats.toNextLevel).to.equal(common.tnl(returnedUser.stats.lvl));
});

it('does not return private paths (and apiToken)', async () => {
let returnedUser = await user.get('/user');

expect(returnedUser.auth.local.hashed_password).to.not.exist;
expect(returnedUser.auth.local.passwordHashMethod).to.not.exist;
expect(returnedUser.auth.local.salt).to.not.exist;
expect(returnedUser.apiToken).to.not.exist;
});

it('returns only user properties requested', async () => {
let returnedUser = await user.get('/user?userFields=achievements,items.mounts');

expect(returnedUser._id).to.equal(user._id);
expect(returnedUser.achievements).to.exist;
expect(returnedUser.items.mounts).to.exist;
// Notifications are always returned
expect(returnedUser.notifications).to.exist;
expect(returnedUser.stats).to.not.exist;
});

it('does not return new inbox messages', async () => {
const otherUser = await generateUser();

await otherUser.post('/members/send-private-message', {
toUserId: user.id,
message: 'first',
});
await otherUser.post('/members/send-private-message', {
toUserId: user.id,
message: 'second',
});
let returnedUser = await user.get('/user');

expect(returnedUser._id).to.equal(user._id);
expect(returnedUser.inbox.messages).to.be.empty;
});
});

0 comments on commit 26c8323

Please sign in to comment.