Merge branch 'develop' into release

This commit is contained in:
Sabe Jones 2020-04-02 15:09:54 -05:00
commit 3ac69d5e75
34 changed files with 618 additions and 250 deletions

View file

@ -0,0 +1,69 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20200402_webhooks_add_protocol';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {
migration: MIGRATION_NAME,
};
if (user && user.webhooks && user.webhooks.length > 0) {
user.webhooks.forEach(webhook => {
// Make sure the protocol is set and valid
if (webhook.url.startsWith('ftp')) {
webhook.url = webhook.url.replace('ftp', 'https');
}
if (!webhook.url.startsWith('http://') && !webhook.url.startsWith('https://')) {
// the default in got 9 was https
// see https://github.com/sindresorhus/got/commit/92bc8082137d7d085750359bbd76c801e213d7d2#diff-0730bb7c2e8f9ea2438b52e419dd86c9L111
webhook.url = `https://${webhook.url}`;
}
});
set.webhooks = user.webhooks;
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({ _id: user._id }, { $set: set }).exec();
}
module.exports = async function processUsers () {
let query = {
migration: { $ne: MIGRATION_NAME },
webhooks: { $exists: true, $not: { $size: 0 } },
};
const fields = {
_id: 1,
webhooks: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1]._id,
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View file

@ -0,0 +1,63 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20200402_webhooks_reenable';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {
migration: MIGRATION_NAME,
};
if (user && user.webhooks && user.webhooks.length > 0) {
user.webhooks.forEach(webhook => {
// Re-enable webhooks disabled because of too many failures
if (webhook.enabled === false && webhook.lastFailureAt === null) {
webhook.enabled = true;
}
});
set.webhooks = user.webhooks;
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({ _id: user._id }, { $set: set }).exec();
}
module.exports = async function processUsers () {
let query = {
migration: { $ne: MIGRATION_NAME },
webhooks: { $exists: true, $not: { $size: 0 } },
};
const fields = {
_id: 1,
webhooks: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1]._id,
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

209
package-lock.json generated
View file

@ -1360,9 +1360,9 @@
}
},
"@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
"integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ=="
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.0.tgz",
"integrity": "sha512-lXKXfypKo644k4Da4yXkPCrwcvn6SlUW2X2zFbuflKHNjf0w9htru01bo26uMhleMXsDmnZ12eJLdrAZa9MANg=="
},
"@sinonjs/commons": {
"version": "1.6.0",
@ -1479,11 +1479,22 @@
}
},
"@szmarczak/http-timer": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
"integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==",
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz",
"integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==",
"requires": {
"defer-to-connect": "^1.0.1"
"defer-to-connect": "^2.0.0"
}
},
"@types/cacheable-request": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz",
"integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==",
"requires": {
"@types/http-cache-semantics": "*",
"@types/keyv": "*",
"@types/node": "*",
"@types/responselike": "*"
}
},
"@types/color-name": {
@ -1507,6 +1518,19 @@
"@types/node": "*"
}
},
"@types/http-cache-semantics": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz",
"integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A=="
},
"@types/keyv": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz",
"integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==",
"requires": {
"@types/node": "*"
}
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@ -1523,6 +1547,14 @@
"integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==",
"optional": true
},
"@types/responselike": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
"requires": {
"@types/node": "*"
}
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -3134,18 +3166,27 @@
"unset-value": "^1.0.0"
}
},
"cacheable-lookup": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz",
"integrity": "sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==",
"requires": {
"@types/keyv": "^3.1.1",
"keyv": "^4.0.0"
}
},
"cacheable-request": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz",
"integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz",
"integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==",
"requires": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^3.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^4.1.0",
"responselike": "^1.0.2"
"responselike": "^2.0.0"
},
"dependencies": {
"get-stream": {
@ -3160,6 +3201,14 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
},
"responselike": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz",
"integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==",
"requires": {
"lowercase-keys": "^2.0.0"
}
}
}
},
@ -4183,6 +4232,7 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
"integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
"optional": true,
"requires": {
"mimic-response": "^1.0.0"
}
@ -4360,9 +4410,9 @@
"integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ="
},
"defer-to-connect": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.0.2.tgz",
"integrity": "sha512-k09hcQcTDY+cwgiwa6PYKLm3jlagNzQ+RSvhjzESOGOx+MNOuXkxTfEvPrO1IOQ81tArCFYQgi631clB70RpQw=="
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz",
"integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg=="
},
"define-properties": {
"version": "1.1.3",
@ -6754,6 +6804,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"optional": true,
"requires": {
"pump": "^3.0.0"
}
@ -7024,21 +7075,82 @@
}
},
"got": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
"integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==",
"version": "10.7.0",
"resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz",
"integrity": "sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==",
"requires": {
"@sindresorhus/is": "^0.14.0",
"@szmarczak/http-timer": "^1.1.2",
"cacheable-request": "^6.0.0",
"decompress-response": "^3.3.0",
"@sindresorhus/is": "^2.0.0",
"@szmarczak/http-timer": "^4.0.0",
"@types/cacheable-request": "^6.0.1",
"cacheable-lookup": "^2.0.0",
"cacheable-request": "^7.0.1",
"decompress-response": "^5.0.0",
"duplexer3": "^0.1.4",
"get-stream": "^4.1.0",
"lowercase-keys": "^1.0.1",
"mimic-response": "^1.0.1",
"p-cancelable": "^1.0.0",
"to-readable-stream": "^1.0.0",
"url-parse-lax": "^3.0.0"
"get-stream": "^5.0.0",
"lowercase-keys": "^2.0.0",
"mimic-response": "^2.1.0",
"p-cancelable": "^2.0.0",
"p-event": "^4.0.0",
"responselike": "^2.0.0",
"to-readable-stream": "^2.0.0",
"type-fest": "^0.10.0"
},
"dependencies": {
"decompress-response": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz",
"integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==",
"requires": {
"mimic-response": "^2.0.0"
}
},
"get-stream": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
"integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==",
"requires": {
"pump": "^3.0.0"
}
},
"lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
},
"mimic-response": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
},
"p-event": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz",
"integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==",
"requires": {
"p-timeout": "^2.0.1"
}
},
"p-timeout": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz",
"integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==",
"requires": {
"p-finally": "^1.0.0"
}
},
"responselike": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz",
"integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==",
"requires": {
"lowercase-keys": "^2.0.0"
}
},
"type-fest": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz",
"integrity": "sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw=="
}
}
},
"graceful-fs": {
@ -7575,9 +7687,9 @@
}
},
"http-cache-semantics": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz",
"integrity": "sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew=="
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
},
"http-errors": {
"version": "1.7.2",
@ -8681,7 +8793,8 @@
"json-buffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
"integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg="
"integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=",
"optional": true
},
"json-content-demux": {
"version": "0.1.3",
@ -8825,11 +8938,18 @@
}
},
"keyv": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
"integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.0.tgz",
"integrity": "sha512-U7ioE8AimvRVLfw4LffyOIRhL2xVgmE8T22L6i0BucSnBUyv4w+I7VN/zVZwRKHOI6ZRUcdMdWHQ8KSUvGpEog==",
"requires": {
"json-buffer": "3.0.0"
"json-buffer": "3.0.1"
},
"dependencies": {
"json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
}
}
},
"kind-of": {
@ -10707,9 +10827,9 @@
}
},
"p-cancelable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
"integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw=="
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz",
"integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg=="
},
"p-defer": {
"version": "1.0.0",
@ -11254,7 +11374,8 @@
"prepend-http": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
"integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc="
"integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
"optional": true
},
"pretty-bytes": {
"version": "5.3.0",
@ -11867,6 +11988,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
"integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=",
"optional": true,
"requires": {
"lowercase-keys": "^1.0.0"
}
@ -13177,9 +13299,9 @@
}
},
"to-readable-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz",
"integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q=="
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz",
"integrity": "sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w=="
},
"to-regex": {
"version": "3.0.2",
@ -13783,6 +13905,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
"integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=",
"optional": true,
"requires": {
"prepend-http": "^2.0.0"
}

View file

@ -30,7 +30,7 @@
"express-basic-auth": "^1.1.5",
"express-validator": "^5.2.0",
"glob": "^7.1.6",
"got": "^9.0.0",
"got": "^10.7.0",
"gulp": "^4.0.0",
"gulp-babel": "^8.0.0",
"gulp-imagemin": "^6.2.0",

View file

@ -121,8 +121,7 @@ describe('emails', () => {
sendTxnEmail(mailingInfo, emailType);
expect(got.post).to.be.calledWith('undefined/job', sinon.match({
json: true,
body: {
json: {
data: {
emailType: sinon.match.same(emailType),
to: sinon.match(value => Array.isArray(value) && value[0].name === mailingInfo.name, 'matches mailing info array'),
@ -154,8 +153,7 @@ describe('emails', () => {
sendTxnEmail(mailingInfo, emailType);
expect(got.post).to.be.calledWith('undefined/job', sinon.match({
json: true,
body: {
json: {
data: {
emailType: sinon.match.same(emailType),
to: sinon.match(val => val[0]._id === mailingInfo._id),
@ -177,8 +175,7 @@ describe('emails', () => {
sendTxnEmail(mailingInfo, emailType, variables);
expect(got.post).to.be.calledWith('undefined/job', sinon.match({
json: true,
body: {
json: {
data: {
variables: sinon.match(value => value[0].name === 'BASE_URL', 'matches variables'),
personalVariables: sinon.match(value => value[0].rcpt === mailingInfo.email

View file

@ -0,0 +1,111 @@
import {
getLanguageFromBrowser,
getLanguageFromUser,
} from '../../../../website/server/libs/language';
import {
generateReq,
} from '../../../helpers/api-unit.helper';
describe('language lib', () => {
let req;
beforeEach(() => {
req = generateReq();
});
describe('getLanguageFromUser', () => {
it('uses the user preferred language if avalaible', () => {
const user = {
preferences: {
language: 'it',
},
};
expect(getLanguageFromUser(user, req)).to.equal('it');
});
it('falls back to english if the user preferred language is not avalaible', () => {
const user = {
preferences: {
language: 'bla',
},
};
expect(getLanguageFromUser(user, req)).to.equal('en');
});
});
describe('getLanguageFromBrowser', () => {
it('uses browser specificed language', () => {
req.headers['accept-language'] = 'pt';
expect(getLanguageFromBrowser(req)).to.equal('pt');
});
it('uses first language in series if browser specifies multiple', () => {
req.headers['accept-language'] = 'he, pt, it';
expect(getLanguageFromBrowser(req)).to.equal('he');
});
it('skips invalid lanaguages and uses first language in series if browser specifies multiple', () => {
req.headers['accept-language'] = 'blah, he, pt, it';
expect(getLanguageFromBrowser(req)).to.equal('he');
});
it('uses normal version of language if specialized locale is passed in', () => {
req.headers['accept-language'] = 'fr-CA';
expect(getLanguageFromBrowser(req)).to.equal('fr');
});
it('uses normal version of language if specialized locale is passed in', () => {
req.headers['accept-language'] = 'fr-CA';
expect(getLanguageFromBrowser(req)).to.equal('fr');
});
it('uses es if es is passed in', () => {
req.headers['accept-language'] = 'es';
expect(getLanguageFromBrowser(req)).to.equal('es');
});
it('uses es_419 if applicable es-languages are passed in', () => {
req.headers['accept-language'] = 'es-mx';
expect(getLanguageFromBrowser(req)).to.equal('es_419');
});
it('uses es_419 if multiple es languages are passed in', () => {
req.headers['accept-language'] = 'es-GT, es-MX, es-CR';
expect(getLanguageFromBrowser(req)).to.equal('es_419');
});
it('zh', () => {
req.headers['accept-language'] = 'zh-TW';
expect(getLanguageFromBrowser(req)).to.equal('zh_TW');
});
it('uses english if browser specified language is not compatible', () => {
req.headers['accept-language'] = 'blah';
expect(getLanguageFromBrowser(req)).to.equal('en');
});
it('uses english if browser does not specify', () => {
req.headers['accept-language'] = '';
expect(getLanguageFromBrowser(req)).to.equal('en');
});
it('uses english if browser does not supply an accept-language header', () => {
delete req.headers['accept-language'];
expect(getLanguageFromBrowser(req)).to.equal('en');
});
});
});

View file

@ -101,8 +101,7 @@ describe('webhooks', () => {
expect(WebhookSender.defaultTransformData).to.be.calledOnce;
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
json: true,
body,
json: body,
});
});
@ -122,7 +121,7 @@ describe('webhooks', () => {
expect(sendWebhook.attachDefaultData).to.be.calledOnce;
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
json: true,
json: body,
});
expect(body).to.eql({
@ -153,8 +152,7 @@ describe('webhooks', () => {
expect(WebhookSender.defaultTransformData).to.not.be.called;
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
json: true,
body: {
json: {
foo: 'bar',
baz: 'biz',
},
@ -271,8 +269,7 @@ describe('webhooks', () => {
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
body,
json: true,
json: body,
});
});
@ -292,8 +289,7 @@ describe('webhooks', () => {
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
body,
json: true,
json: body,
});
});
@ -316,12 +312,10 @@ describe('webhooks', () => {
expect(got.post).to.be.calledTwice;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
body,
json: true,
json: body,
});
expect(got.post).to.be.calledWithMatch('http://other-url.com', {
body,
json: true,
json: body,
});
});
@ -351,8 +345,7 @@ describe('webhooks', () => {
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
json: true,
body,
json: body,
});
await sleep(0.1);
@ -368,8 +361,7 @@ describe('webhooks', () => {
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
json: true,
body,
json: body,
});
await sleep(0.1);
@ -459,8 +451,7 @@ describe('webhooks', () => {
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
json: true,
body: {
json: {
type: 'scored',
webhookType: 'taskActivity',
user: {
@ -497,8 +488,7 @@ describe('webhooks', () => {
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://global-activity.com', {
json: true,
body: {
json: {
type: 'scored',
webhookType: 'taskActivity',
user: {
@ -551,8 +541,7 @@ describe('webhooks', () => {
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
json: true,
body: {
json: {
type,
webhookType: 'taskActivity',
user: {
@ -592,8 +581,7 @@ describe('webhooks', () => {
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
json: true,
body: {
json: {
webhookType: 'taskActivity',
user: {
_id: user._id,
@ -633,8 +621,7 @@ describe('webhooks', () => {
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[2].url, {
json: true,
body: {
json: {
type,
webhookType: 'userActivity',
user: {
@ -680,8 +667,7 @@ describe('webhooks', () => {
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[1].url, {
json: true,
body: {
json: {
type,
webhookType: 'questActivity',
user: {
@ -727,8 +713,7 @@ describe('webhooks', () => {
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch(webhooks[webhooks.length - 1].url, {
json: true,
body: {
json: {
webhookType: 'groupChatReceived',
user: {
_id: user._id,

View file

@ -19,7 +19,7 @@ describe('analytics middleware', () => {
next = generateNext();
});
it('attaches analytics object res.locals', () => {
it('attaches analytics object to res', () => {
const attachAnalytics = requireAgain(pathToAnalyticsMiddleware).default;
attachAnalytics(req, res, next);

View file

@ -21,28 +21,11 @@ describe('cron middleware', () => {
req;
let user;
beforeEach(done => {
beforeEach(async () => {
res = generateRes();
req = generateReq();
user = new User({
auth: {
local: {
username: 'username',
lowerCaseUsername: 'username',
email: 'email@email.email',
salt: 'salt',
hashed_password: 'hashed_password', // eslint-disable-line camelcase
},
},
});
user.save()
.then(savedUser => {
res.locals.user = savedUser;
res.analytics = analyticsService;
done();
})
.catch(done);
user = await res.locals.user.save();
res.analytics = analyticsService;
});
afterEach(() => {

View file

@ -12,6 +12,9 @@ import { model as User } from '../../../../website/server/models/user';
const { i18n } = common;
// TODO some of the checks here can be simplified to simply check
// that the right parameters are passed to the functions in libs/language
describe('language middleware', () => {
describe('res.t', () => {
let res; let req; let
@ -19,6 +22,8 @@ describe('language middleware', () => {
beforeEach(() => {
res = generateRes();
// remove the defaul user
res.locals.user = undefined;
req = generateReq();
next = generateNext();
@ -57,6 +62,8 @@ describe('language middleware', () => {
beforeEach(() => {
res = generateRes();
// remove the defaul user
res.locals.user = undefined;
req = generateReq();
next = generateNext();
attachTranslateFunction(req, res, next);
@ -88,7 +95,7 @@ describe('language middleware', () => {
lang: 'es',
};
req.locals = {
res.locals = {
user: {
preferences: {
language: 'it',
@ -108,7 +115,7 @@ describe('language middleware', () => {
context('authorized request', () => {
it('uses the user preferred language if avalaible', () => {
req.locals = {
res.locals = {
user: {
preferences: {
language: 'it',
@ -122,7 +129,7 @@ describe('language middleware', () => {
});
it('falls back to english if the user preferred language is not avalaible', done => {
req.locals = {
res.locals = {
user: {
preferences: {
language: 'bla',
@ -138,7 +145,7 @@ describe('language middleware', () => {
});
it('uses the user preferred language even if a session is included in request', () => {
req.locals = {
res.locals = {
user: {
preferences: {
language: 'it',

View file

@ -33,7 +33,7 @@
'resting': showRestingBanner
}"
>
<banned-account-modal />
<!-- <banned-account-modal /> -->
<amazon-payments-modal v-if="!isStaticPage" />
<payments-success-modal />
<sub-cancel-modal-confirm v-if="isUserLoaded" />
@ -266,7 +266,6 @@ import {
} from '@/libs/userlocalManager';
import svgClose from '@/assets/svg/close.svg';
import bannedAccountModal from '@/components/bannedAccountModal';
const COMMUNITY_MANAGER_EMAIL = process.env.EMAILS_COMMUNITY_MANAGER_EMAIL; // eslint-disable-line
@ -281,7 +280,6 @@ export default {
BuyModal,
SelectMembersModal,
amazonPaymentsModal,
bannedAccountModal,
paymentsSuccessModal,
subCancelModalConfirm,
subCanceledModal,
@ -385,7 +383,8 @@ export default {
return response;
}, error => {
if (error.response.status >= 400) {
this.checkForBannedUser(error);
const isBanned = this.checkForBannedUser(error);
if (isBanned === true) return null; // eslint-disable-line consistent-return
// Don't show errors from getting user details. These users have delete their account,
// but their chat message still exists.
@ -403,7 +402,8 @@ export default {
// TODO use a specific error like NotificationNotFound instead of checking for the string
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
if (invalidUserMessage.indexOf(errorMessage) !== -1) {
this.$store.dispatch('auth:logout');
this.$store.dispatch('auth:logout', { redirectToLogin: true });
return null;
}
// Most server errors should return is click to dismiss errors, with some exceptions
@ -553,7 +553,7 @@ export default {
// Case where user is not logged in
if (!parseSettings) {
return;
return false;
}
const bannedMessage = this.$t('accountSuspended', {
@ -561,9 +561,10 @@ export default {
userId: parseSettings.auth.apiId,
});
if (errorMessage !== bannedMessage) return;
if (errorMessage !== bannedMessage) return false;
this.$root.$emit('bv::show::modal', 'banned-account');
this.$store.dispatch('auth:logout', { redirectToLogin: true });
return true;
},
initializeModalStack () {
// Manage modals

View file

@ -186,7 +186,7 @@ export default {
return this.overrideTopPadding;
}
let val = '27px';
let val = '24px';
if (!this.avatarOnly) {
if (this.member.items.currentPet) val = '24px';

View file

@ -348,6 +348,7 @@
></div>
</div>
</div>
</div>
<div
v-if="achievementsCategories[key].number > 5"
class="btn btn-flat btn-show-more"
@ -358,7 +359,6 @@
$t('showAllAchievements', {category: $t(key+'Achievs')})
}}
</div>
</div>
</div>
</div>
<hr class="col-12">

View file

@ -82,7 +82,8 @@ export async function socialAuth (store, params) {
localStorage.setItem(LOCALSTORAGE_AUTH_KEY, userLocalData);
}
export function logout () {
export function logout (store, options = {}) {
localStorage.clear();
window.location.href = '/logout-server';
const query = options.redirectToLogin === true ? '?redirectToLogin=true' : '';
window.location.href = `/logout-server${query}`;
}

View file

@ -78,7 +78,7 @@ context('avatar.vue', () => {
};
});
it('defaults to 27px', () => {
xit('defaults to 27px', () => {
vm.avatarOnly = true;
expect(vm.paddingTop).to.equal('27px');
});

View file

@ -72,6 +72,8 @@
"achievementTickledPink": "Rosige Bäckchen",
"foundNewItems": "Du hast neue Gegenstände gefunden!",
"foundNewItemsCTA": "Schau in Dein Inventar und versuche, Dein neues Schlüpfelixier mit einem Ei zu kombinieren!",
"foundNewItemsExplanation": "Durch das Abschließen von Aufgaben erhältst Du die Chance Gegenstände, wie etwa Eier, Schlüpfelixiere und Futter, zu finden.",
"achievementBugBonanza": "Kostbarer Käfer"
"foundNewItemsExplanation": "Durch das Abschließen von Aufgaben erhältst Du die Chance, Gegenstände wie etwa Eier, Schlüpfelixiere und Futter zu finden.",
"achievementBugBonanza": "Kostbarer Käfer",
"achievementBugBonanzaModalText": "Du hast die Käfer-, Schmetterling-, Schnecken- und Spinnenhaustier-Quests erfüllt!",
"achievementBugBonanzaText": "Hat die Käfer-, Schmetterling-, Schnecken- und Spinnenhaustier-Quests erfüllt."
}

View file

@ -4,7 +4,7 @@
"armoireText": "Verzauberter Schrank",
"armoireNotesFull": "Öffne den Schrank, um zufällig spezielle Ausrüstung, Erfahrung oder Futter zu erhalten! Verbleibende Ausrüstungsgegenstände:",
"armoireLastItem": "Du hast das letzte Stück seltener Ausrüstung im verzauberten Schrank gefunden.",
"armoireNotesEmpty": "Im verzauberten Schrank gibt es jeweils in der ersten Woche eines Monats neue Ausrüstung. Bis dahin, klicke weiter für Erfahrung und Futter!",
"armoireNotesEmpty": "Im verzauberten Schrank gibt es jeweils in der ersten Woche des Monats neue Ausrüstung. Bis dahin klicke weiter für Erfahrung und Futter!",
"dropEggWolfText": "Wolfsjunges",
"dropEggWolfMountText": "Wolfs-Reittier",
"dropEggWolfAdjective": "ein treues",
@ -354,5 +354,6 @@
"premiumPotionUnlimitedNotes": "Nicht auf Eier von Quest-Haustieren anwendbar.",
"hatchingPotionAmber": "Bernstein",
"hatchingPotionAurora": "Polarlicht",
"hatchingPotionRuby": "Rubinrotes"
"hatchingPotionRuby": "Rubinrotes",
"hatchingPotionBirchBark": "Birkenborke"
}

View file

@ -85,45 +85,45 @@
"scarecrowWarriorSet": "Vogelscheuchenkrieger (Krieger)",
"stitchWitchSet": "Stichhexe (Magier)",
"potionerSet": "Tränkebrauer (Heiler)",
"battleRogueSet": "Kampfschurke (Schurke)",
"battleRogueSet": "Kampf-Fleder (Schurke)",
"springingBunnySet": "Hüpfendes Häschen (Heiler)",
"grandMalkinSet": "Prächtiger Kater (Magier)",
"cleverDogSet": "Schlauer Hund (Schurke)",
"braveMouseSet": "Mutige Maus (Krieger)",
"summer2016SharkWarriorSet": "Haifisch-Krieger (Krieger)",
"summer2016DolphinMageSet": "Delfin-Magier (Magier)",
"summer2016SeahorseHealerSet": "Seepferdchen-Heiler (Heiler)",
"summer2016EelSet": "Zitteraal-Schurke (Schurke)",
"summer2016SharkWarriorSet": "Haifisch(Krieger)",
"summer2016DolphinMageSet": "Delfin (Magier)",
"summer2016SeahorseHealerSet": "Seepferdchen (Heiler)",
"summer2016EelSet": "Zitteraal (Schurke)",
"fall2016SwampThingSet": "Das Ding aus dem Sumpf (Krieger)",
"fall2016WickedSorcererSet": "Boshafter Zauberer (Magier)",
"fall2016GorgonHealerSet": "Gorgonen-Heiler (Heiler)",
"fall2016BlackWidowSet": "Schurkische Schwarze Witwe (Schurke)",
"fall2016GorgonHealerSet": "Gorgone (Heiler)",
"fall2016BlackWidowSet": "Schwarze Witwe (Schurke)",
"winter2017IceHockeySet": "Eishockey (Krieger)",
"winter2017WinterWolfSet": "Winterwolf (Magier)",
"winter2017SugarPlumSet": "Zuckerpflaumen-Heiler (Heiler)",
"winter2017FrostyRogueSet": "Frostiger Schurke (Schurke)",
"spring2017FelineWarriorSet": "Katzenhafter Krieger (Krieger)",
"winter2017SugarPlumSet": "Zuckerpflaume (Heiler)",
"winter2017FrostyRogueSet": "Frosty (Schurke)",
"spring2017FelineWarriorSet": "Katzenhaft (Krieger)",
"spring2017CanineConjurorSet": "Bellender Beschwörer (Magier)",
"spring2017FloralMouseSet": "Blumenmaus (Heiler)",
"spring2017SneakyBunnySet": "Raffiniertes Häschen (Schurke)",
"summer2017SandcastleWarriorSet": "Sandburg-Krieger (Krieger)",
"summer2017WhirlpoolMageSet": "Whirlpool-Magier (Magier)",
"summer2017SandcastleWarriorSet": "Sandburg (Krieger)",
"summer2017WhirlpoolMageSet": "Whirlpool (Magier)",
"summer2017SeashellSeahealerSet": "Muschel-Meeresheiler (Heiler)",
"summer2017SeaDragonSet": "Seedrache (Schurke)",
"fall2017HabitoweenSet": "Habitoween-Krieger (Krieger)",
"fall2017MasqueradeSet": "Maskerade-Magier (Magier)",
"fall2017HauntedHouseSet": "Geisterhaus-Heiler (Heiler)",
"fall2017TrickOrTreatSet": "Süßes-oder-Saures-Schurke (Schurke)",
"winter2018ConfettiSet": "Konfettimagier (Magier)",
"winter2018GiftWrappedSet": "Geschenkpapierverpackter Krieger (Krieger)",
"winter2018MistletoeSet": "Mistelzweigheiler (Heiler)",
"winter2018ReindeerSet": "Rentier-Schurke (Schurke)",
"spring2018SunriseWarriorSet": "Sonnenaufgang-Krieger (Krieger)",
"spring2018TulipMageSet": "Tulpenmagier (Magier)",
"spring2018GarnetHealerSet": "Granatheiler (Heiler)",
"spring2018DucklingRogueSet": "Entchen-Schurke (Schurke)",
"summer2018BettaFishWarriorSet": "Kampffisch-Krieger (Krieger)",
"summer2018LionfishMageSet": "Feuerfisch-Magier (Magier)",
"fall2017HabitoweenSet": "Habitoween (Krieger)",
"fall2017MasqueradeSet": "Maskerade (Magier)",
"fall2017HauntedHouseSet": "Geisterhaus (Heiler)",
"fall2017TrickOrTreatSet": "Süßes oder Saures (Schurke)",
"winter2018ConfettiSet": "Konfetti (Magier)",
"winter2018GiftWrappedSet": "Geschenkpapierverpackt (Krieger)",
"winter2018MistletoeSet": "Mistelzweig(Heiler)",
"winter2018ReindeerSet": "Rentier (Schurke)",
"spring2018SunriseWarriorSet": "Sonnenaufgang (Krieger)",
"spring2018TulipMageSet": "Tulpe (Magier)",
"spring2018GarnetHealerSet": "Granat (Heiler)",
"spring2018DucklingRogueSet": "Entchen (Schurke)",
"summer2018BettaFishWarriorSet": "Kampffisch (Krieger)",
"summer2018LionfishMageSet": "Feuerfisch (Magier)",
"summer2018MerfolkMonarchSet": "Meervolk-Monarch (Heiler)",
"summer2018FisherRogueSet": "Fischdieb (Schurke)",
"fall2018MinotaurWarriorSet": "Minotaurus (Krieger)",
@ -173,5 +173,10 @@
"winter2020WinterSpiceSet": "Wintergewürz (Heiler)",
"winter2020CarolOfTheMageSet": "Weihnachtslied des Magiers (Magier)",
"winter2020EvergreenSet": "Immergrün (Krieger)",
"decemberYYYY": "Dezember <%= year %>"
"decemberYYYY": "Dezember <%= year %>",
"spring2020BeetleWarriorSet": "Nashornkäfer (Krieger)",
"marchYYYY": "März <%= year %>",
"spring2020LapisLazuliRogueSet": "Lapislazuli (Schurke)",
"spring2020IrisHealerSet": "Iris (Heiler)",
"spring2020PuddleMageSet": "Pfütze (Magier)"
}

View file

@ -247,5 +247,6 @@
"subMonths": "Monate abonniert",
"subscriptionStats": "Abonnenten-Attributwerte",
"doubleDropCap": "Verdopple die Beute",
"mysterySet202003": "Stachliges Streitgewandset"
"mysterySet202003": "Stachliges Streitgewandset",
"mysterySet202004": "Mächtiger-Monarch-Set"
}

View file

@ -46,7 +46,7 @@
"hatchingPotion": "hatching potion",
"noHatchingPotions": "You don't have any hatching potions.",
"inventoryText": "Click an egg to see usable potions highlighted in green and then click one of the highlighted potions to hatch your pet. If no potions are highlighted, click that egg again to deselect it, and instead click a potion first to have the usable eggs highlighted. You can also sell unwanted drops to Alexander the Merchant.",
"haveHatchablePet": "You have a <%= potion %> hatching potion and <%= egg %> egg to hatch this pet! <b>Click</b> the paw print to hatch.",
"haveHatchablePet": "You have a <%= potion %> hatching potion and <%= egg %> egg to hatch this pet! <b>Click</b> to hatch!",
"quickInventory": "Quick Inventory",
"food": "Pet Food and Saddles",
"noFoodAvailable": "You don't have any Pet Food.",

View file

@ -2079,5 +2079,9 @@
"weaponSpecialSpring2020WarriorNotes": "Fight or flight, this wing will serve you well! Increases Strength by <%= str %>. Limited Edition 2020 Spring Gear.",
"weaponSpecialSpring2020WarriorText": "Sharpened Wing",
"weaponSpecialSpring2020RogueNotes": "You'll strike so fast it'll look even MORE blue! Increases Strength by <%= str %>. Limited Edition 2020 Spring Gear.",
"weaponSpecialSpring2020RogueText": "Lazurite Blade"
"weaponSpecialSpring2020RogueText": "Lazurite Blade",
"headAccessoryMystery202004Notes": "They twitch just a bit if the scent of flowers drifts by--use them to find a pretty garden! Confers no benefit. April 2020 Subscriber Item.",
"headAccessoryMystery202004Text": "Mighty Monarch Antennae",
"backMystery202004Notes": "Make a quick flutter to the nearest flowery meadow or migrate across the continent with these beautiful wings! Confers no benefit. April 2020 Subscriber Item.",
"backMystery202004Text": "Mighty Monarch Wings"
}

View file

@ -247,5 +247,6 @@
"monthlyMysteryItems": "Monthly Mystery Items",
"subscribersReceiveBenefits": "Subscribers receive these useful benefits!",
"giftASubscription": "Gift a Subscription",
"mysterySet202003": "Barbed Battler Set"
"mysterySet202003": "Barbed Battler Set",
"mysterySet202004": "Mighty Monarch Set"
}

View file

@ -2079,5 +2079,9 @@
"weaponSpecialSpring2020WarriorNotes": "Lutar ou voar, esta asa irá te atender bem! Aumenta a Força em <%= str %>. Equipamento de edição limitada da primavera de 2020.",
"weaponSpecialSpring2020WarriorText": "Asa afiada",
"weaponSpecialSpring2020RogueNotes": "Você atacará tão rápido que ficará ainda MAIS AZUL! Aumenta a Força em <%= str %>. Equipamento de edição limitada da primavera de 2020.",
"weaponSpecialSpring2020RogueText": "Lâmina de lazurita"
"weaponSpecialSpring2020RogueText": "Lâmina de lazurita",
"headAccessoryMystery202004Notes": "Elas tremem um pouco se o perfume das flores passa perto -- use-as para encontrar um belo jardim! Não confere benefícios. Item de assinante, Abril de 2020.",
"headAccessoryMystery202004Text": "Antenas do(a) Monarca poderoso(a)",
"backMystery202004Notes": "Faça um movimento rápido para o prado florido mais próximo ou migre pelo continente com essas lindas asas! Não confere benefícios. Item de assinante, Abril de 2020.",
"backMystery202004Text": "Asas do(a) Monarca poderoso(a)"
}

View file

@ -247,5 +247,6 @@
"monthlyMysteryItems": "Itens misteriosos mensalmente",
"subscribersReceiveBenefits": "Assinantes recebem esses benefícios úteis!",
"giftASubscription": "Presentar uma Assinatura",
"mysterySet202003": "Conjunto do(a) gladiador(a) farpado(a)"
"mysterySet202003": "Conjunto do(a) gladiador(a) farpado(a)",
"mysterySet202004": "Conjunto do(a) Monarca poderoso(a)"
}

View file

@ -2079,5 +2079,7 @@
"armorSpecialSpring2020RogueText": "群青装甲",
"armorSpecialSpring2020WarriorText": "外骨骼护甲",
"armorSpecialSpring2020MageText": "旋风长袍",
"armorSpecialSpring2020HealerText": "防护的花瓣"
"armorSpecialSpring2020HealerText": "防护的花瓣",
"headAccessoryMystery202004Text": "强大的君主斑蝶触角",
"backMystery202004Text": "强大的君主斑蝶翅膀"
}

View file

@ -247,5 +247,6 @@
"monthlyMysteryItems": "每月神秘物品",
"subscribersReceiveBenefits": "订阅者可以获得这些优越的好处!",
"mysterySet202003": "倒刺斗士套装",
"giftASubscription": "赠送订阅"
"giftASubscription": "赠送订阅",
"mysterySet202004": "强大的君主斑蝶套装"
}

View file

@ -28,7 +28,9 @@ api.logout = {
async handler (req, res) {
if (req.logout) req.logout(); // passportjs method
req.session = null;
res.redirect('/');
const redirectUrl = req.query.redirectToLogin === 'true' ? '/login' : '/';
res.redirect(redirectUrl);
},
};

View file

@ -133,9 +133,9 @@ export async function sendTxn (mailingInfoArray, emailType, variables, personalV
return got.post(`${EMAIL_SERVER.url}/job`, {
retry: 5, // retry the http request to the email server 5 times
timeout: 60000, // wait up to 60s before timing out
auth: `${EMAIL_SERVER.auth.user}:${EMAIL_SERVER.auth.password}`,
json: true,
body: {
username: EMAIL_SERVER.auth.user,
password: EMAIL_SERVER.auth.password,
json: {
type: 'email',
data: {
emailType,
@ -149,7 +149,7 @@ export async function sendTxn (mailingInfoArray, emailType, variables, personalV
backoff: { delay: 10 * 60 * 1000, type: 'fixed' },
},
},
}).catch(err => logger.error(err));
}).json().catch(err => logger.error(err, 'Error while sending an email.'));
}
return null;

View file

@ -22,34 +22,10 @@ const momentLangsMapping = {
};
export const approvedLanguages = [
'bg',
'cs',
'da',
'de',
'en',
'en_GB',
'en@pirate',
'es',
'es_419',
'fr',
'he',
'hu',
'id',
'it',
'ja',
'nl',
'pl',
'pt',
'pt_BR',
'ro',
'ru',
'sk',
'sr',
'sv',
'tr',
'uk',
'zh',
'zh_TW',
'bg', 'cs', 'da', 'de', 'en', 'en_GB', 'en@pirate',
'es', 'es_419', 'fr', 'he', 'hu', 'id', 'it',
'ja', 'nl', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk',
'sr', 'sv', 'tr', 'uk', 'zh', 'zh_TW',
];
function _loadTranslations (locale) {

View file

@ -0,0 +1,52 @@
import accepts from 'accepts';
import _ from 'lodash';
import {
translations,
defaultLangCodes,
multipleVersionsLanguages,
} from './i18n';
function getUniqueListOfLanguages (languages) {
const acceptableLanguages = _(languages).map(lang => lang.slice(0, 2)).uniq().value();
const uniqueListOfLanguages = _.intersection(acceptableLanguages, defaultLangCodes);
return uniqueListOfLanguages;
}
function checkForApplicableLanguageVariant (originalLanguageOptions) {
const languageVariant = _.find(originalLanguageOptions, accepted => {
const trimmedAccepted = accepted.slice(0, 2);
return multipleVersionsLanguages[trimmedAccepted];
});
return languageVariant;
}
export function getLanguageFromBrowser (req) {
const originalLanguageOptions = accepts(req).languages();
const uniqueListOfLanguages = getUniqueListOfLanguages(originalLanguageOptions);
const baseLanguage = (uniqueListOfLanguages[0] || '').toLowerCase();
const languageMapping = multipleVersionsLanguages[baseLanguage];
if (languageMapping) {
let languageVariant = checkForApplicableLanguageVariant(originalLanguageOptions);
if (languageVariant) {
languageVariant = languageVariant.toLowerCase();
} else {
return 'en';
}
return languageMapping[languageVariant] || baseLanguage;
}
return baseLanguage || 'en';
}
export function getLanguageFromUser (user, req) {
const preferredLang = user && user.preferences && user.preferences.language;
const lang = translations[preferredLang] ? preferredLang : getLanguageFromBrowser(req);
return lang;
}

View file

@ -13,10 +13,10 @@ function sendWebhook (webhook, body, user) {
const { url, lastFailureAt } = webhook;
got.post(url, {
body,
json: true,
json: body,
timeout: 30000, // wait up to 30s before timing out
retry: 3, // retry the request up to 3 times
// Not calling .json() to parse the response because we simply ignore it
}).catch(webhookErr => {
// Log the error
logger.error(webhookErr, 'Error while sending a webhook request.');

View file

@ -7,6 +7,8 @@ import {
model as User,
} from '../models/user';
import gcpStackdriverTracer from '../libs/gcpTraceAgent';
import common from '../../common';
import { getLanguageFromUser } from '../libs/language';
const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL');
const USER_FIELDS_ALWAYS_LOADED = ['_id', 'notifications', 'preferences', 'auth', 'flags'];
@ -72,7 +74,17 @@ export function authWithHeaders (options = {}) {
.exec()
.then(user => {
if (!user) throw new NotAuthorized(res.t('invalidCredentials'));
if (user.auth.blocked) throw new NotAuthorized(res.t('accountSuspended', { communityManagerEmail: COMMUNITY_MANAGER_EMAIL, userId: user._id }));
if (user.auth.blocked) {
// We want the accountSuspended message to be translated but the language
// middleware hasn't run yet so we pick it manually
const language = getLanguageFromUser(user, req);
throw new NotAuthorized(common.i18n.t('accountSuspended', {
communityManagerEmail: COMMUNITY_MANAGER_EMAIL,
userId: user._id,
}, language));
}
res.locals.user = user;
req.session.userId = user._id;

View file

@ -1,60 +1,15 @@
import accepts from 'accepts';
import _ from 'lodash';
import { model as User } from '../models/user';
import common from '../../common';
import {
translations,
defaultLangCodes,
multipleVersionsLanguages,
} from '../libs/i18n';
import {
getLanguageFromUser,
getLanguageFromBrowser,
} from '../libs/language';
const { i18n } = common;
function _getUniqueListOfLanguages (languages) {
const acceptableLanguages = _(languages).map(lang => lang.slice(0, 2)).uniq().value();
const uniqueListOfLanguages = _.intersection(acceptableLanguages, defaultLangCodes);
return uniqueListOfLanguages;
}
function _checkForApplicableLanguageVariant (originalLanguageOptions) {
const languageVariant = _.find(originalLanguageOptions, accepted => {
const trimmedAccepted = accepted.slice(0, 2);
return multipleVersionsLanguages[trimmedAccepted];
});
return languageVariant;
}
function _getFromBrowser (req) {
const originalLanguageOptions = accepts(req).languages();
const uniqueListOfLanguages = _getUniqueListOfLanguages(originalLanguageOptions);
const baseLanguage = (uniqueListOfLanguages[0] || '').toLowerCase();
const languageMapping = multipleVersionsLanguages[baseLanguage];
if (languageMapping) {
let languageVariant = _checkForApplicableLanguageVariant(originalLanguageOptions);
if (languageVariant) {
languageVariant = languageVariant.toLowerCase();
} else {
return 'en';
}
return languageMapping[languageVariant] || baseLanguage;
}
return baseLanguage || 'en';
}
function _getFromUser (user, req) {
const preferredLang = user && user.preferences && user.preferences.language;
const lang = translations[preferredLang] ? preferredLang : _getFromBrowser(req);
return lang;
}
export function attachTranslateFunction (req, res, next) {
res.t = function reqTranslation (...args) {
return i18n.t(...args, req.language);
@ -64,26 +19,33 @@ export function attachTranslateFunction (req, res, next) {
}
export function getUserLanguage (req, res, next) {
if (req.query.lang) { // In case the language is specified in the request url, use it
// In case the language is specified in the request url, use intersection
if (req.query.lang) {
req.language = translations[req.query.lang] ? req.query.lang : 'en';
return next();
}
// If the request is authenticated, use the user's preferred language
} if (req.locals && req.locals.user) {
req.language = _getFromUser(req.locals.user, req);
if (res.locals && res.locals.user) {
req.language = getLanguageFromUser(res.locals.user, req);
return next();
} if (req.session && req.session.userId) { // Same thing if the user has a valid session
}
// Same thing if the user has a valid session
if (req.session && req.session.userId) {
return User.findOne({
_id: req.session.userId,
}, 'preferences.language')
.lean()
.exec()
.then(user => {
req.language = _getFromUser(user, req);
req.language = getLanguageFromUser(user, req);
return next();
})
.catch(next);
} // Otherwise get from browser
req.language = _getFromUser(null, req);
}
// Otherwise get from browser
req.language = getLanguageFromBrowser(req);
return next();
}

View file

@ -58,6 +58,8 @@ export const schema = new Schema({
required: true,
validate: [v => validator.isURL(v, {
require_tld: !!IS_PRODUCTION, // eslint-disable-line camelcase
require_protocol: true, // TODO migrate existing ones
protocols: ['http', 'https'],
}), shared.i18n.t('invalidUrl')],
},
enabled: { $type: Boolean, required: true, default: true },