-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move inbox to its own model (#10428)
* 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
Showing
61 changed files
with
3,173 additions
and
1,094 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); | ||
}); |
Oops, something went wrong.