mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 15:48:04 +01:00
Compare commits
5 Commits
fiz/respon
...
phillip/ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6092cba1ea | ||
|
|
0b4adbbf07 | ||
|
|
8faa5b0582 | ||
|
|
95494c685b | ||
|
|
10978d46ab |
@@ -1,12 +1,13 @@
|
|||||||
import gulp from 'gulp';
|
import gulp from 'gulp';
|
||||||
import clean from 'rimraf';
|
import { rimraf as clean } from 'rimraf';
|
||||||
import apidoc from 'apidoc';
|
import apidoc from 'apidoc';
|
||||||
|
|
||||||
const APIDOC_DEST_PATH = './apidoc/html';
|
const APIDOC_DEST_PATH = './apidoc/html';
|
||||||
const APIDOC_SRC_PATH = './website/server';
|
const APIDOC_SRC_PATH = './website/server';
|
||||||
const APIDOC_CONFIG_PATH = './apidoc/apidoc.json';
|
const APIDOC_CONFIG_PATH = './apidoc/apidoc.json';
|
||||||
gulp.task('apidoc:clean', done => {
|
gulp.task('apidoc:clean', done => {
|
||||||
clean(APIDOC_DEST_PATH, done);
|
clean.sync(APIDOC_DEST_PATH);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('apidoc', gulp.series('apidoc:clean', done => {
|
gulp.task('apidoc', gulp.series('apidoc:clean', done => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import babel from 'gulp-babel';
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import spawn from 'cross-spawn'; // eslint-disable-line import/no-extraneous-dependencies
|
import spawn from 'cross-spawn'; // eslint-disable-line import/no-extraneous-dependencies
|
||||||
import clean from 'rimraf';
|
import { rimraf as clean } from 'rimraf';
|
||||||
|
|
||||||
gulp.task('build:babel:server', () => gulp.src('website/server/**/*.js')
|
gulp.task('build:babel:server', () => gulp.src('website/server/**/*.js')
|
||||||
.pipe(babel())
|
.pipe(babel())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import gulp from 'gulp';
|
import gulp from 'gulp';
|
||||||
import spritesmith from 'gulp.spritesmith';
|
import spritesmith from 'gulp.spritesmith';
|
||||||
import clean from 'rimraf';
|
import { rimraf as clean } from 'rimraf';
|
||||||
import mergeStream from 'merge-stream';
|
import mergeStream from 'merge-stream';
|
||||||
import { sync } from 'glob';
|
import { sync } from 'glob';
|
||||||
|
|
||||||
@@ -109,7 +109,8 @@ gulp.task('sprites:main', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('sprites:clean', done => {
|
gulp.task('sprites:clean', done => {
|
||||||
clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done);
|
clean.sync(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('sprites:compile', gulp.series('sprites:clean', 'sprites:main', done => done()));
|
gulp.task('sprites:compile', gulp.series('sprites:clean', 'sprites:main', done => done()));
|
||||||
|
|||||||
Submodule habitica-images updated: e3215f16f9...aa72332019
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"version": "5.41.0",
|
"version": "5.41.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"version": "5.41.0",
|
"version": "5.41.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.22.10",
|
"@babel/core": "^7.22.10",
|
||||||
|
|||||||
@@ -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.41.0",
|
"version": "5.41.1",
|
||||||
"main": "./website/server/index.js",
|
"main": "./website/server/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.22.10",
|
"@babel/core": "^7.22.10",
|
||||||
@@ -29,9 +29,9 @@
|
|||||||
"eslint": "^8.55.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-config-habitrpg": "^6.2.3",
|
"eslint-config-habitrpg": "^6.2.3",
|
||||||
"eslint-plugin-mocha": "^5.0.0",
|
"eslint-plugin-mocha": "^5.0.0",
|
||||||
"express": "^4.21.1",
|
"express": "5.1.0",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"express-validator": "^5.2.0",
|
"express-validator": "7.2.1",
|
||||||
"firebase-admin": "^12.1.1",
|
"firebase-admin": "^12.1.1",
|
||||||
"glob": "^8.1.0",
|
"glob": "^8.1.0",
|
||||||
"got": "^11.8.6",
|
"got": "^11.8.6",
|
||||||
@@ -66,8 +66,8 @@
|
|||||||
"rate-limiter-flexible": "^2.4.2",
|
"rate-limiter-flexible": "^2.4.2",
|
||||||
"redis": "^3.1.2",
|
"redis": "^3.1.2",
|
||||||
"remove-markdown": "^0.5.0",
|
"remove-markdown": "^0.5.0",
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"short-uuid": "^4.2.2",
|
"short-uuid": "^4.2.2",
|
||||||
|
"rimraf": "6.0.1",
|
||||||
"sinon": "^15.2.0",
|
"sinon": "^15.2.0",
|
||||||
"stripe": "^12.18.0",
|
"stripe": "^12.18.0",
|
||||||
"superagent": "^8.1.2",
|
"superagent": "^8.1.2",
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ describe('highlightMentions', () => {
|
|||||||
expect(result[0]).to.equal('[@user-dash](/profile/444): message [@user_underscore](/profile/555)');
|
expect(result[0]).to.equal('[@user-dash](/profile/444): message [@user_underscore](/profile/555)');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('highlights users with case-insensitive matching', async () => {
|
||||||
|
const text = '@USER: message @User2 @USER3';
|
||||||
|
const result = await highlightMentions(text);
|
||||||
|
expect(result[0]).to.equal('[@USER](/profile/111): message [@User2](/profile/222) [@USER3](/profile/333)');
|
||||||
|
});
|
||||||
|
|
||||||
it('doesn\'t highlight nonexisting users', async () => {
|
it('doesn\'t highlight nonexisting users', async () => {
|
||||||
const text = '@nouser message';
|
const text = '@nouser message';
|
||||||
const result = await highlightMentions(text);
|
const result = await highlightMentions(text);
|
||||||
|
|||||||
@@ -238,6 +238,18 @@ describe('POST /chat', () => {
|
|||||||
expect(groupMessages[0].id).to.exist;
|
expect(groupMessages[0].id).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('creates a chat with case-insensitive mentions', async () => {
|
||||||
|
const originalUsername = member.auth.local.username;
|
||||||
|
const uppercaseUsername = originalUsername.toUpperCase();
|
||||||
|
const messageWithMentions = `hi @${uppercaseUsername}`;
|
||||||
|
const newMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: messageWithMentions });
|
||||||
|
const groupMessages = await user.get(`/groups/${groupWithChat._id}/chat`);
|
||||||
|
|
||||||
|
expect(newMessage.message.id).to.exist;
|
||||||
|
expect(newMessage.message.text).to.include(`[@${uppercaseUsername}](/profile/${member._id})`);
|
||||||
|
expect(groupMessages[0].id).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
it('creates a chat with a max length of 3000 chars', async () => {
|
it('creates a chat with a max length of 3000 chars', async () => {
|
||||||
const veryLongMessage = `
|
const veryLongMessage = `
|
||||||
123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789.
|
123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789.
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ describe('POST /user/auth/local/login', () => {
|
|||||||
})).to.eventually.be.rejected.and.eql({
|
})).to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'), userId: user._id, username: user.auth.local.username }),
|
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'), userId: user._id }),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -203,9 +203,6 @@ export default {
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
}, error => { // Set up Error interceptors
|
}, error => { // Set up Error interceptors
|
||||||
if (!error.response) {
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
if (error.response.status >= 400) {
|
if (error.response.status >= 400) {
|
||||||
const isBanned = this.checkForBannedUser(error);
|
const isBanned = this.checkForBannedUser(error);
|
||||||
if (isBanned === true) return null; // eslint-disable-line consistent-return
|
if (isBanned === true) return null; // eslint-disable-line consistent-return
|
||||||
|
|||||||
9
website/client/src/assets/images/gifts_bg.svg
Normal file
9
website/client/src/assets/images/gifts_bg.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg width="378" height="176" viewBox="0 0 378 176" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0H378V174C378 175.105 377.105 176 376 176H1.99999C0.895423 176 0 175.105 0 174V0Z" fill="url(#paint0_linear_2257_239)"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_2257_239" x1="378" y1="0" x2="0" y2="0" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#925CF3"/>
|
||||||
|
<stop offset="1" stop-color="#34B5C1"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 448 B |
37
website/client/src/assets/images/gifts_start.svg
Normal file
37
website/client/src/assets/images/gifts_start.svg
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<svg width="48" height="96" viewBox="0 0 48 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M-3.10104 12.0483C-2.82088 9.43721 -3.53422 6.57214 -5.6115 5.24584C-7.68877 3.91954 -9.89543 4.92709 -10.1422 6.808C-10.3891 8.68891 -9.06061 9.83066 -4.97737 13.9337C-3.81821 15.0985 -3.3812 14.6594 -3.10104 12.0483Z" stroke="#FFA624" stroke-width="4"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.34089 15.2054C4.45116 13.6561 7.27707 12.8443 9.45877 13.9889C11.6405 15.1334 11.8754 17.5575 10.3778 18.7127C8.88016 19.868 7.23193 19.2828 1.65411 17.781C0.0706697 17.3546 0.230624 16.7548 2.34089 15.2054Z" stroke="#FFBE5D" stroke-width="4"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.549002 12.0098C-3.61871 9.59194 -3.87667 15.8322 -2.20457 16.8023C-0.532473 17.7724 4.71671 14.4277 0.549002 12.0098Z" fill="#EE9109"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M-1.76917 16.0445L13.637 24.9825L9.18965 32.7229L-6.21656 23.785L-1.76917 16.0445Z" fill="#F8F9F9"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M-6.90457 13.0652L3.36623 19.0238L-1.08116 26.7643L-11.352 20.8057L-6.90457 13.0652Z" fill="#FFBE5D"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M-1.76917 16.0445L3.36623 19.0238L1.88377 21.604L-3.25163 18.6247L-1.76917 16.0445Z" fill="#FFA624"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M-6.21656 23.785L6.62195 31.2333L-3.75529 49.2944L-16.5938 41.8461L-6.21656 23.785Z" fill="#F8F9F9"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M-3.64886 25.2747L6.62195 31.2333L5.13948 33.8134L-5.13132 27.8548L-3.64886 25.2747Z" fill="#DDF3F3"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.401307 24.1842L10.6721 30.1428L9.18965 32.7229L-1.08116 26.7643L0.401307 24.1842Z" fill="#DDF3F3"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.7924 38.4607L17.9387 42.0519L21.31 40.5834L24.8838 41.4413L23.4225 38.0537L24.2762 34.4625L20.9049 35.9309L17.3311 35.0731L18.7924 38.4607Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M-3.93867 71.2331L-4.79238 74.8243L-1.42111 73.3559L2.15271 74.2137L0.691383 70.8261L1.54509 67.2349L-1.82618 68.7033L-5.4 67.8455L-3.93867 71.2331Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.8949 25.3807L35.0583 29.8802L37.9424 26.2452L42.4202 25.0761L38.8028 22.178L37.6393 17.6786L34.7552 21.3135L30.2775 22.4826L33.8949 25.3807Z" fill="white" fill-opacity="0.5"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.2596 71.999L40.579 68.1435L45.9507 88.2881L31.6312 92.1436L26.2596 71.999Z" fill="#F8F9F9"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9401 75.8545L26.2589 71.9966L31.6273 92.1421L17.3084 96L11.9401 75.8545Z" fill="#DDF3F3"/>
|
||||||
|
<rect width="2.96589" height="20.8485" transform="matrix(0.965611 -0.25999 0.257652 0.966238 23.3957 72.7701)" fill="#FFA624"/>
|
||||||
|
<rect width="2.96589" height="20.8485" transform="matrix(0.965611 -0.25999 0.257652 0.966238 26.2596 71.999)" fill="#FFBE5D"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.9999 90.0369L30.8638 89.2658L31.6312 92.1436L28.7673 92.9147L27.9999 90.0369Z" fill="#EE9109"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3957 72.7701L26.2596 71.999L27.0269 74.8768L24.163 75.6479L23.3957 72.7701Z" fill="#EE9109"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9401 75.8545L23.3951 72.7682L24.162 75.6461L12.707 78.7325L11.9401 75.8545Z" fill="#C1E9E9"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5443 93.1213L27.9999 90.0369L28.7673 92.9147L17.3117 95.9991L16.5443 93.1213Z" fill="#C1E9E9"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.1235 71.2279L40.579 68.1435L41.3464 71.0213L29.8908 74.1057L29.1235 71.2279Z" fill="#DDF3F3"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.7277 88.4947L45.1833 85.4103L45.9507 88.2881L34.4951 91.3725L33.7277 88.4947Z" fill="#DDF3F3"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.8638 89.2658L33.7277 88.4947L34.4951 91.3725L31.6312 92.1436L30.8638 89.2658Z" fill="#FFA624"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.2596 71.999L29.1235 71.2279L29.8908 74.1057L27.0269 74.8768L26.2596 71.999Z" fill="#FFA624"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.5224 56.3076C25.8087 53.7812 24.0792 51.3933 21.6588 50.9455C19.2383 50.4977 17.5679 52.2625 18.0403 54.0994C18.5126 55.9363 20.17 56.4948 25.4855 58.7621C26.9945 59.4057 27.236 58.834 26.5224 56.3076Z" stroke="#FFA624" stroke-width="4"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.745 57.1864C34.124 54.9555 36.4415 53.1391 38.8911 53.3791C41.3406 53.6191 42.4621 55.7782 41.5042 57.413C40.5463 59.0479 38.7999 59.1258 33.0684 59.8329C31.4413 60.0337 31.366 59.4173 32.745 57.1864Z" stroke="#FFBE5D" stroke-width="4"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.8923 54.898C25.1267 54.225 27.2139 60.108 29.1258 60.378C31.0378 60.648 34.6579 55.571 29.8923 54.898Z" fill="#EE9109"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.247 59.5115L46.8635 61.9994L45.6255 70.8503L28.0091 68.3625L29.247 59.5115Z" fill="#F8F9F9"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.6306 57.0236L29.247 59.5114L28.0091 68.3624L10.3927 65.8745L11.6306 57.0236Z" fill="#DDF3F3"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3749 58.6822L35.1192 60.3408L33.8813 69.1917L22.137 67.5332L23.3749 58.6822Z" fill="#FFBE5D"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3749 58.6822L29.247 59.5115L28.0091 68.3625L22.137 67.5332L23.3749 58.6822Z" fill="#FFA624"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.247 59.5115L35.1192 60.3408L34.7065 63.2911L28.8344 62.4618L29.247 59.5115Z" fill="#FFA624"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3749 58.6822L29.247 59.5115L28.8344 62.4618L22.9622 61.6326L23.3749 58.6822Z" fill="#EE9109"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8053 62.9241L22.5496 64.5827L22.137 67.533L10.3927 65.8745L10.8053 62.9241Z" fill="#C1E9E9"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.2939 66.2414L46.0382 67.9L45.6255 70.8503L33.8813 69.1917L34.2939 66.2414Z" fill="#DDF3F3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 6.0 KiB |
@@ -30,12 +30,23 @@
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
color: $gray-200;
|
color: $gray-200;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
box-shadow: none;
|
background-color: transparent;
|
||||||
background-color: $gray-700;
|
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
|
box-shadow:
|
||||||
|
0 1px 3px 0 rgba($black, 0.12),
|
||||||
|
0 1px 2px 0 rgba($black, 0.24);
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
min-height: 32px;
|
||||||
|
max-height: 32px;
|
||||||
|
gap: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
.svg {
|
.svg {
|
||||||
color: $gray-300;
|
color: $gray-200;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
website/client/src/assets/svg/close-white.svg
Normal file
8
website/client/src/assets/svg/close-white.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g fill="#FFFFFF" fill-rule="nonzero">
|
||||||
|
<polygon points="12.1973467 2 14 3.80265326 9.80187117 8 14 12.1973467 12.1973467 14 8 9.80187117 3.80265326 14 2 12.1973467 6.19812883 8 2 3.80265326 3.80265326 2 8 6.19812883"></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 504 B |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1,29 @@
|
|||||||
|
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.1792 31.6843L46.8536 22.3769L23.918 28.6988L18.861 42.5218L44.341 58.5813L58.1792 31.6843Z" fill="#FF944C"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.6218 34.5148L46.1108 26.1328L36.2812 28.8422L46.6218 34.5148Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M30.2393 39.0304L26.4518 31.5515L36.2813 28.8422L30.2393 39.0304Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M46.6218 34.5148L36.2813 28.8422L30.2393 39.0304L46.6218 34.5148Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M53.8301 32.5279L46.1108 26.1328L46.6218 34.5148L53.8301 32.5279Z" fill="white"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M23.0309 41.0173L26.4518 31.5516L30.2393 39.0304L23.0309 41.0173Z" fill="#FA8537"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M53.8301 32.5279L46.6218 34.5148L43.0424 53.79L53.8301 32.5279Z" fill="#FA8537"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M23.0309 41.0173L30.2393 39.0304L43.0425 53.79L23.0309 41.0173Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.6218 34.5148L30.2393 39.0304L43.0425 53.79L46.6218 34.5148Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M50.555 4.15937L47.026 0.420004L38.7773 1.59601L36.4144 6.17539L44.5675 12.8919L50.555 4.15937Z" fill="#FFBE5D"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.414 4.62854L46.6034 1.6924L43.0682 2.1964L46.414 4.62854Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M40.5221 5.46854L39.5331 2.7004L43.0682 2.1964L40.5221 5.46854Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M46.414 4.62854L43.0683 2.1964L40.5221 5.46855L46.414 4.62854Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M49.0064 4.25894L46.6034 1.6924L46.414 4.62854L49.0064 4.25894Z" fill="white"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M37.9296 5.83815L39.5331 2.70041L40.5221 5.46855L37.9296 5.83815Z" fill="#FFA624"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M49.0064 4.25893L46.414 4.62853L44.3259 11.1688L49.0064 4.25893Z" fill="#FFA624"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M37.9297 5.83815L40.5221 5.46855L44.326 11.1688L37.9297 5.83815Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M46.414 4.62854L40.5221 5.46855L44.326 11.1688L46.414 4.62854Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.2986 16.7775L24.6513 8.36623L11.1016 3.94533L4.07056 9.19883L11.614 25.6769L27.2986 16.7775Z" fill="#FF6165"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M20.5864 14.3719L23.0573 10.0026L17.2502 8.10789L20.5864 14.3719Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M10.908 11.2141L11.4432 6.21322L17.2502 8.10789L10.908 11.2141Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M20.5864 14.3719L17.2502 8.10789L10.9081 11.2141L20.5864 14.3719Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M24.8449 15.7613L23.0573 10.0026L20.5864 14.3719L24.8449 15.7613Z" fill="white"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M6.64955 9.82464L11.4432 6.21321L10.908 11.2141L6.64955 9.82464Z" fill="#F23035"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M24.8449 15.7613L20.5864 14.3719L12.5221 22.8464L24.8449 15.7613Z" fill="#F23035"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M6.64959 9.82464L10.9081 11.2141L12.5221 22.8463L6.64959 9.82464Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M20.5864 14.3719L10.9081 11.2141L12.5221 22.8463L20.5864 14.3719Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,29 @@
|
|||||||
|
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.82083 31.6843L17.1464 22.3769L40.082 28.6988L45.139 42.5218L19.659 58.5813L5.82083 31.6843Z" fill="#24CC8F"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.3782 34.5148L17.8892 26.1328L27.7188 28.8422L17.3782 34.5148Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M33.7607 39.0304L37.5482 31.5515L27.7187 28.8422L33.7607 39.0304Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M17.3782 34.5148L27.7187 28.8422L33.7607 39.0304L17.3782 34.5148Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M10.1699 32.5279L17.8892 26.1328L17.3782 34.5148L10.1699 32.5279Z" fill="white"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M40.9691 41.0173L37.5482 31.5516L33.7607 39.0304L40.9691 41.0173Z" fill="#1CA372"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M10.1699 32.5279L17.3782 34.5148L20.9576 53.79L10.1699 32.5279Z" fill="#1CA372"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M40.9691 41.0173L33.7607 39.0304L20.9575 53.79L40.9691 41.0173Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.3782 34.5148L33.7607 39.0304L20.9575 53.79L17.3782 34.5148Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.445 4.15937L16.974 0.420004L25.2227 1.59601L27.5856 6.17539L19.4325 12.8919L13.445 4.15937Z" fill="#925CF3"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.586 4.62854L17.3966 1.6924L20.9318 2.1964L17.586 4.62854Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M23.4779 5.46854L24.4669 2.7004L20.9318 2.1964L23.4779 5.46854Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M17.586 4.62854L20.9317 2.1964L23.4779 5.46855L17.586 4.62854Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M14.9936 4.25894L17.3966 1.6924L17.586 4.62854L14.9936 4.25894Z" fill="white"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M26.0704 5.83815L24.4669 2.70041L23.4779 5.46855L26.0704 5.83815Z" fill="#4F2A93"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M14.9936 4.25893L17.586 4.62853L19.6741 11.1688L14.9936 4.25893Z" fill="#4F2A93"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M26.0703 5.83815L23.4779 5.46855L19.674 11.1688L26.0703 5.83815Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M17.586 4.62854L23.4779 5.46855L19.674 11.1688L17.586 4.62854Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.7014 16.7775L39.3487 8.36623L52.8984 3.94533L59.9294 9.19883L52.386 25.6769L36.7014 16.7775Z" fill="#50B5E9"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M43.4136 14.3719L40.9427 10.0026L46.7498 8.10789L43.4136 14.3719Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M53.092 11.2141L52.5568 6.21322L46.7498 8.10789L53.092 11.2141Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M43.4136 14.3719L46.7498 8.10789L53.0919 11.2141L43.4136 14.3719Z" fill="white"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M39.1551 15.7613L40.9427 10.0026L43.4136 14.3719L39.1551 15.7613Z" fill="white"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M57.3504 9.82464L52.5568 6.21321L53.092 11.2141L57.3504 9.82464Z" fill="#46A7D9"/>
|
||||||
|
<path opacity="0.35" fill-rule="evenodd" clip-rule="evenodd" d="M39.1551 15.7613L43.4136 14.3719L51.4779 22.8464L39.1551 15.7613Z" fill="#46A7D9"/>
|
||||||
|
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M57.3504 9.82464L53.0919 11.2141L51.4779 22.8463L57.3504 9.82464Z" fill="white"/>
|
||||||
|
<path opacity="0.25" fill-rule="evenodd" clip-rule="evenodd" d="M43.4136 14.3719L53.0919 11.2141L51.4779 22.8463L43.4136 14.3719Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -117,7 +117,7 @@ export default {
|
|||||||
closeWithAction () {
|
closeWithAction () {
|
||||||
this.close();
|
this.close();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.$router.push({ name: 'achievements' });
|
this.$router.push(`/profile/${this.$store.state.user.data._id}#achievements`);
|
||||||
}, 200);
|
}, 200);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -43,11 +43,9 @@ export default {
|
|||||||
const AUTH_SETTINGS = localStorage.getItem(LOCALSTORAGE_AUTH_KEY);
|
const AUTH_SETTINGS = localStorage.getItem(LOCALSTORAGE_AUTH_KEY);
|
||||||
const parseSettings = JSON.parse(AUTH_SETTINGS);
|
const parseSettings = JSON.parse(AUTH_SETTINGS);
|
||||||
const userId = parseSettings ? parseSettings.auth.apiId : '';
|
const userId = parseSettings ? parseSettings.auth.apiId : '';
|
||||||
const username = this.$store?.state?.user?.data?.auth?.local?.username || '';
|
|
||||||
|
|
||||||
return this.$t('accountSuspended', {
|
return this.$t('accountSuspended', {
|
||||||
userId,
|
userId,
|
||||||
username,
|
|
||||||
communityManagerEmail: COMMUNITY_MANAGER_EMAIL,
|
communityManagerEmail: COMMUNITY_MANAGER_EMAIL,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -72,32 +72,40 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6 text-right">
|
<div class="col-12 col-md-6 text-right">
|
||||||
<div
|
<div
|
||||||
class="box member-count"
|
class="box member-count p-2"
|
||||||
@click="showMemberModal()"
|
@click="showMemberModal()"
|
||||||
>
|
>
|
||||||
<div
|
<div class="box-content">
|
||||||
class="svg-icon member-icon"
|
<div class="icon-number-row">
|
||||||
v-html="icons.memberIcon"
|
<div
|
||||||
></div>
|
class="svg-icon member-icon"
|
||||||
{{ challenge.memberCount }}
|
v-html="icons.memberIcon"
|
||||||
<div
|
></div>
|
||||||
v-once
|
<span class="number">{{ challenge.memberCount }}</span>
|
||||||
class="details"
|
</div>
|
||||||
>
|
<div
|
||||||
{{ $t('participantsTitle') }}
|
v-once
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
{{ $t('participantsTitle') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box prize-count p-2">
|
||||||
<div
|
<div class="box-content">
|
||||||
class="svg-icon gem-icon"
|
<div class="icon-number-row">
|
||||||
v-html="icons.gemIcon"
|
<div
|
||||||
></div>
|
class="svg-icon gem-icon"
|
||||||
{{ challenge.prize || 0 }}
|
v-html="icons.gemIcon"
|
||||||
<div
|
></div>
|
||||||
v-once
|
<span class="number">{{ challenge.prize || 0 }}</span>
|
||||||
class="details"
|
</div>
|
||||||
>
|
<div
|
||||||
{{ $t('prize') }}
|
v-once
|
||||||
|
class="details"
|
||||||
|
>
|
||||||
|
{{ $t('prize') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -304,7 +312,6 @@
|
|||||||
|
|
||||||
.box {
|
.box {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 1em;
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||||
@@ -314,22 +321,88 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&.member-count:hover {
|
&.member-count:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.box-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-number-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 0.1em;
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: normal;
|
||||||
|
margin-left: 0.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: .2em;
|
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 0.4em;
|
|
||||||
color: $gray-200;
|
color: $gray-200;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 4px;
|
||||||
|
line-height: 1.15;
|
||||||
|
word-break: break-word;
|
||||||
|
max-height: 2.3em;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.member-count {
|
||||||
|
.icon-number-row {
|
||||||
|
.svg-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.1;
|
||||||
|
max-height: 2.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.prize-count {
|
||||||
|
.icon-number-row {
|
||||||
|
.svg-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.1;
|
||||||
|
max-height: 2.2em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
id="close-challenge-modal"
|
id="close-challenge-modal"
|
||||||
:title="$t('endChallenge')"
|
:title="$t('endChallenge')"
|
||||||
size="md"
|
size="md"
|
||||||
|
:hide-header="false"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
slot="modal-header"
|
slot="modal-header"
|
||||||
@@ -15,6 +16,9 @@
|
|||||||
>
|
>
|
||||||
{{ $t('endChallenge') }}
|
{{ $t('endChallenge') }}
|
||||||
</h2>
|
</h2>
|
||||||
|
<close-x
|
||||||
|
@close="$root.$emit('bv::hide::modal', 'close-challenge-modal')"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row text-center">
|
<div class="row text-center">
|
||||||
<span
|
<span
|
||||||
@@ -28,28 +32,67 @@
|
|||||||
class="col-12"
|
class="col-12"
|
||||||
>
|
>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="support-habitica">
|
<div class="badge-section">
|
||||||
<!-- @TODO: Add challenge achievement badge here-->
|
<div
|
||||||
|
class="gems-left"
|
||||||
|
v-html="icons.gemsOrange"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="challenge-badge"
|
||||||
|
v-html="icons.endChallengeBadge"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="gems-right"
|
||||||
|
v-html="icons.gemsPurple"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<strong v-once>{{ $t('selectChallengeWinnersDescription') }}</strong>
|
<strong v-once>{{ $t('selectChallengeWinnersDescription') }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12 search-input-container">
|
||||||
<member-search-dropdown
|
<div class="search-input-wrapper">
|
||||||
:text="winnerText"
|
<div
|
||||||
:members="members"
|
class="search-icon"
|
||||||
:challenge-id="challengeId"
|
v-html="icons.search"
|
||||||
@member-selected="selectMember"
|
></div>
|
||||||
/>
|
<input
|
||||||
|
v-model="searchTerm"
|
||||||
|
class="search-input"
|
||||||
|
type="text"
|
||||||
|
placeholder="@Username"
|
||||||
|
@input="searchMembers"
|
||||||
|
@focus="showResults = true"
|
||||||
|
@blur="handleBlur"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="showResults && filteredMembers.length > 0"
|
||||||
|
class="search-results"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="member in filteredMembers"
|
||||||
|
:key="member._id"
|
||||||
|
class="search-result-item"
|
||||||
|
@mousedown="selectMember(member)"
|
||||||
|
>
|
||||||
|
{{ getMemberDisplayName(member) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<button
|
<button
|
||||||
v-once
|
class="btn award-winner-btn"
|
||||||
class="btn btn-primary"
|
:class="{'has-winner': winner._id}"
|
||||||
|
:disabled="!winner._id"
|
||||||
@click="closeChallenge"
|
@click="closeChallenge"
|
||||||
>
|
>
|
||||||
{{ $t('awardWinners') }}
|
<span>{{ $t('awardWinners') }}</span>
|
||||||
|
<div
|
||||||
|
class="gem-icon"
|
||||||
|
v-html="icons.gem"
|
||||||
|
></div>
|
||||||
|
<span>{{ prize }} {{ prize === 1 ? $t('gem') : $t('gems') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
@@ -60,14 +103,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<strong v-once>{{ $t('doYouWantedToDeleteChallenge') }}</strong>
|
<strong
|
||||||
|
v-once
|
||||||
|
class="delete-challenge-text"
|
||||||
|
>{{ $t('doYouWantedToDeleteChallenge') }}</strong>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-once
|
||||||
|
class="col-12 refund-text"
|
||||||
|
>
|
||||||
|
{{ $t('deleteChallengeRefundDescription') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<button
|
<button
|
||||||
v-once
|
v-once
|
||||||
class="btn btn-danger"
|
class="btn btn-danger delete-challenge-btn"
|
||||||
@click="deleteChallenge()"
|
@click="deleteChallenge()"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="svg-icon color delete-icon"
|
||||||
|
v-html="icons.deleteIcon"
|
||||||
|
></div>
|
||||||
{{ $t('deleteChallenge') }}
|
{{ $t('deleteChallenge') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,6 +138,7 @@
|
|||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss'>
|
||||||
@import '@/assets/scss/colors.scss';
|
@import '@/assets/scss/colors.scss';
|
||||||
|
@import '@/assets/scss/button.scss';
|
||||||
|
|
||||||
#close-challenge-modal {
|
#close-challenge-modal {
|
||||||
h2 {
|
h2 {
|
||||||
@@ -94,26 +151,190 @@
|
|||||||
|
|
||||||
.header-wrap {
|
.header-wrap {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-top: 2em;
|
padding-top: 32px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.support-habitica {
|
.modal-close {
|
||||||
background-image: url('@/assets/svg/for-css/support-habitica-gems.svg?raw');
|
position: absolute;
|
||||||
width: 325px;
|
right: 16px;
|
||||||
height: 89px;
|
top: 16px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-container {
|
||||||
|
margin-top: 1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 384px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-55%);
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
color: $gray-200;
|
||||||
|
pointer-events: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 32px;
|
||||||
|
padding-left: 36px;
|
||||||
|
padding-right: 12px;
|
||||||
|
border: 1px solid $gray-400;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.2s ease, border-width 0.2s ease;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border: 2px solid $purple-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: $gray-300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-results {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: $white;
|
||||||
|
border: 1px solid $gray-400;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.search-result-item {
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $purple-600;
|
||||||
|
color: $purple-300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-challenge-text {
|
||||||
|
color: $maroon-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refund-text {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: $gray-50;
|
||||||
|
margin-top: 0.5em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-challenge-btn {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.delete-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.award-winner-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:not(:disabled) {
|
||||||
|
background-color: $white;
|
||||||
|
color: $gray-200;
|
||||||
|
border: 1px solid $gray-400;
|
||||||
|
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||||
|
|
||||||
|
&.has-winner {
|
||||||
|
background-color: $purple-200;
|
||||||
|
color: $white;
|
||||||
|
border-color: $purple-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.has-winner) {
|
||||||
|
background-color: $gray-700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gem-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: $gems-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin: -24px auto 0;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
|
||||||
|
.gems-left, .gems-right {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.challenge-badge {
|
||||||
|
width: 48px;
|
||||||
|
height: 52px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-footer, .modal-header {
|
.modal-footer, .modal-header {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.footer-wrap {
|
.footer-wrap {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-12 {
|
.col-12 {
|
||||||
margin-top: 2em;
|
margin-top: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-12:first-child {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.or {
|
.or {
|
||||||
@@ -123,21 +344,39 @@
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
color: $gray-100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import memberSearchDropdown from '@/components/members/memberSearchDropdown';
|
import searchIcon from '@/assets/svg/for-css/search.svg?raw';
|
||||||
|
import deleteIcon from '@/assets/svg/delete.svg?raw';
|
||||||
|
import gemIcon from '@/assets/svg/gem.svg?raw';
|
||||||
|
import endChallengeBadge from '@/assets/svg/for-css/end_challenge_badge.svg?raw';
|
||||||
|
import gemsOrange from '@/assets/svg/for-css/orange100_red100_yellow100_gems.svg?raw';
|
||||||
|
import gemsPurple from '@/assets/svg/for-css/purple200_green10_blue100_gems.svg?raw';
|
||||||
|
import closeX from '@/components/ui/closeX';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
memberSearchDropdown,
|
closeX,
|
||||||
},
|
},
|
||||||
props: ['challengeId', 'members', 'prize', 'flagCount'],
|
props: ['challengeId', 'members', 'prize', 'flagCount'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
winner: {},
|
winner: {},
|
||||||
|
searchTerm: '',
|
||||||
|
showResults: false,
|
||||||
|
filteredMembers: [],
|
||||||
|
icons: Object.freeze({
|
||||||
|
search: searchIcon,
|
||||||
|
deleteIcon,
|
||||||
|
gem: gemIcon,
|
||||||
|
endChallengeBadge,
|
||||||
|
gemsOrange,
|
||||||
|
gemsPurple,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -150,8 +389,35 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
searchMembers () {
|
||||||
|
if (!this.searchTerm) {
|
||||||
|
this.filteredMembers = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchLower = this.searchTerm.toLowerCase().replace('@', '');
|
||||||
|
this.filteredMembers = this.members.filter(member => {
|
||||||
|
const username = member.auth?.local?.username || '';
|
||||||
|
const displayName = member.profile?.name || '';
|
||||||
|
return username.toLowerCase().includes(searchLower)
|
||||||
|
|| displayName.toLowerCase().includes(searchLower);
|
||||||
|
}).slice(0, 10);
|
||||||
|
},
|
||||||
|
getMemberDisplayName (member) {
|
||||||
|
if (member.auth?.local?.username) {
|
||||||
|
return `@${member.auth.local.username}`;
|
||||||
|
}
|
||||||
|
return member.profile?.name || '';
|
||||||
|
},
|
||||||
selectMember (member) {
|
selectMember (member) {
|
||||||
this.winner = member;
|
this.winner = member;
|
||||||
|
this.searchTerm = this.getMemberDisplayName(member);
|
||||||
|
this.showResults = false;
|
||||||
|
},
|
||||||
|
handleBlur () {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showResults = false;
|
||||||
|
}, 200);
|
||||||
},
|
},
|
||||||
async closeChallenge () {
|
async closeChallenge () {
|
||||||
this.challenge = await this.$store.dispatch('challenges:selectChallengeWinner', {
|
this.challenge = await this.$store.dispatch('challenges:selectChallengeWinner', {
|
||||||
|
|||||||
@@ -1,37 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="notification d-flex flex-column justify-content-center text-center"
|
class="notification d-flex justify-content-center align-items-center"
|
||||||
>
|
>
|
||||||
<strong
|
<img
|
||||||
v-once
|
src="@/assets/images/gifts_start.svg"
|
||||||
class="mx-auto mb-2"
|
class="gift-start"
|
||||||
|
alt=""
|
||||||
>
|
>
|
||||||
{{ $t('g1g1') }}
|
<div class="content-wrapper d-flex flex-column justify-content-center text-center">
|
||||||
</strong>
|
<strong
|
||||||
<small
|
|
||||||
v-once
|
|
||||||
class="mx-4 mb-3"
|
|
||||||
>
|
|
||||||
{{ $t('g1g1Details') }}
|
|
||||||
</small>
|
|
||||||
<div
|
|
||||||
class="btn-secondary mx-auto d-flex"
|
|
||||||
@click="showSelectUser()"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-once
|
v-once
|
||||||
class="m-auto"
|
class="mx-auto mb-2"
|
||||||
|
>
|
||||||
|
{{ $t('g1g1') }}
|
||||||
|
</strong>
|
||||||
|
<small
|
||||||
|
v-once
|
||||||
|
class="mx-4 mb-3"
|
||||||
|
>
|
||||||
|
{{ $t('g1g1Details') }}
|
||||||
|
</small>
|
||||||
|
<button
|
||||||
|
class="btn btn-secondary mx-auto"
|
||||||
|
@click="showSelectUser()"
|
||||||
>
|
>
|
||||||
{{ $t('sendGift') }}
|
{{ $t('sendGift') }}
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<img
|
||||||
|
src="@/assets/images/gifts_start.svg"
|
||||||
|
class="gift-end"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="notification-remove"
|
class="close-x"
|
||||||
@click.stop="remove()"
|
@click="remove()"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-once
|
class="svg-icon svg-close"
|
||||||
class="svg-icon"
|
|
||||||
v-html="icons.close"
|
v-html="icons.close"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,51 +47,89 @@
|
|||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
@import '@/assets/scss/colors.scss';
|
@import '@/assets/scss/colors.scss';
|
||||||
|
|
||||||
small, strong {
|
small {
|
||||||
color: $white;
|
color: $white;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.714;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: $white;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.714;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
background-image: url('@/assets/images/g1g1-notif.png');
|
background-image: url('@/assets/images/gifts_bg.svg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
height: 10rem;
|
height: 10rem;
|
||||||
padding: 3rem;
|
padding: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-remove {
|
.content-wrapper {
|
||||||
position: absolute;
|
flex: 1;
|
||||||
width: 18px;
|
padding: 2rem;
|
||||||
height: 18px;
|
z-index: 1;
|
||||||
padding: 4px;
|
|
||||||
right: 24px;
|
|
||||||
top: 24px;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.gift-start {
|
||||||
width: 5.75rem;
|
height: 96px;
|
||||||
min-height: 1.5rem;
|
width: auto;
|
||||||
border-radius: 2px;
|
position: absolute;
|
||||||
border-color: $white;
|
left: 0;
|
||||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
top: 50%;
|
||||||
font-size: 12px;
|
transform: translateY(-50%);
|
||||||
font-weight: bold;
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-end {
|
||||||
|
height: 96px;
|
||||||
|
width: auto;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%) scaleX(-1);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-x {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
&:hover .svg-close {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-close {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import closeIcon from '@/assets/svg/close-teal.svg?raw';
|
import closeIcon from '@/assets/svg/close-white.svg?raw';
|
||||||
import { mapActions } from '@/libs/store';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['notification'],
|
props: ['notification', 'eventKey'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
@@ -94,11 +138,11 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
|
||||||
readNotification: 'notifications:readNotification',
|
|
||||||
}),
|
|
||||||
remove () {
|
remove () {
|
||||||
this.readNotification({ notificationId: this.notification.id });
|
if (this.eventKey) {
|
||||||
|
window.sessionStorage.setItem(`hide-g1g1-${this.eventKey}`, 'true');
|
||||||
|
}
|
||||||
|
this.$emit('notification-removed');
|
||||||
},
|
},
|
||||||
showSelectUser () {
|
showSelectUser () {
|
||||||
this.$root.$emit('bv::show::modal', 'select-user-modal');
|
this.$root.$emit('bv::show::modal', 'select-user-modal');
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export default {
|
|||||||
props: ['notification', 'canRemove'],
|
props: ['notification', 'canRemove'],
|
||||||
methods: {
|
methods: {
|
||||||
action () {
|
action () {
|
||||||
this.$router.push({ name: 'achievements' });
|
this.$router.push(`/profile/${this.$store.state.user.data._id}#achievements`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
action () {
|
action () {
|
||||||
this.$router.push({ name: 'stats' });
|
this.$router.push(`/profile/${this.$store.state.user.data._id}#stats`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,6 +49,12 @@
|
|||||||
v-if="showOnboardingGuide"
|
v-if="showOnboardingGuide"
|
||||||
:never-seen="hasSpecialBadge"
|
:never-seen="hasSpecialBadge"
|
||||||
/>
|
/>
|
||||||
|
<gift-one-get-one-notification
|
||||||
|
v-if="shouldShowG1g1"
|
||||||
|
:notification="g1g1Notification"
|
||||||
|
:event-key="g1g1EventKey"
|
||||||
|
@notification-removed="handleG1g1Removed"
|
||||||
|
/>
|
||||||
<component
|
<component
|
||||||
:is="notification.type"
|
:is="notification.type"
|
||||||
v-for="notification in notifications"
|
v-for="notification in notifications"
|
||||||
@@ -114,6 +120,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import * as quests from '@/../../common/script/content/quests';
|
import * as quests from '@/../../common/script/content/quests';
|
||||||
import { hasCompletedOnboarding } from '@/../../common/script/libs/onboarding';
|
import { hasCompletedOnboarding } from '@/../../common/script/libs/onboarding';
|
||||||
|
import find from 'lodash/find';
|
||||||
import { mapState, mapActions } from '@/libs/store';
|
import { mapState, mapActions } from '@/libs/store';
|
||||||
import notificationsIcon from '@/assets/svg/notifications.svg?raw';
|
import notificationsIcon from '@/assets/svg/notifications.svg?raw';
|
||||||
import MenuDropdown from '../ui/customMenuDropdown';
|
import MenuDropdown from '../ui/customMenuDropdown';
|
||||||
@@ -151,6 +158,7 @@ export default {
|
|||||||
CARD_RECEIVED,
|
CARD_RECEIVED,
|
||||||
CHALLENGE_INVITATION,
|
CHALLENGE_INVITATION,
|
||||||
GIFT_ONE_GET_ONE,
|
GIFT_ONE_GET_ONE,
|
||||||
|
GiftOneGetOneNotification: GIFT_ONE_GET_ONE,
|
||||||
GROUP_TASK_ASSIGNED,
|
GROUP_TASK_ASSIGNED,
|
||||||
GROUP_TASK_CLAIMED,
|
GROUP_TASK_CLAIMED,
|
||||||
GROUP_TASK_NEEDS_WORK,
|
GROUP_TASK_NEEDS_WORK,
|
||||||
@@ -178,17 +186,14 @@ export default {
|
|||||||
hasSpecialBadge: false,
|
hasSpecialBadge: false,
|
||||||
quests,
|
quests,
|
||||||
openStatus: undefined,
|
openStatus: undefined,
|
||||||
|
g1g1Hidden: false,
|
||||||
actionableNotifications: [
|
actionableNotifications: [
|
||||||
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
|
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
|
||||||
'QUEST_INVITATION',
|
'QUEST_INVITATION',
|
||||||
],
|
],
|
||||||
// A list of notifications handled by this component,
|
|
||||||
// listed in the order they should appear in the notifications panel.
|
|
||||||
// NOTE: Those not listed here won't be shown in the notification panel!
|
|
||||||
handledNotifications: [
|
handledNotifications: [
|
||||||
'NEW_STUFF',
|
'NEW_STUFF',
|
||||||
'ITEM_RECEIVED',
|
'ITEM_RECEIVED',
|
||||||
'GIFT_ONE_GET_ONE',
|
|
||||||
'GROUP_TASK_NEEDS_WORK',
|
'GROUP_TASK_NEEDS_WORK',
|
||||||
'GUILD_INVITATION',
|
'GUILD_INVITATION',
|
||||||
'PARTY_INVITATION',
|
'PARTY_INVITATION',
|
||||||
@@ -207,7 +212,10 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({ user: 'user.data' }),
|
...mapState({
|
||||||
|
user: 'user.data',
|
||||||
|
currentEventList: 'worldState.data.currentEventList',
|
||||||
|
}),
|
||||||
notificationsOrder () {
|
notificationsOrder () {
|
||||||
// Returns a map of NOTIFICATION_TYPE -> POSITION
|
// Returns a map of NOTIFICATION_TYPE -> POSITION
|
||||||
const orderMap = {};
|
const orderMap = {};
|
||||||
@@ -286,9 +294,9 @@ export default {
|
|||||||
|
|
||||||
return notifications;
|
return notifications;
|
||||||
},
|
},
|
||||||
// The total number of notification, shown inside the dropdown
|
|
||||||
notificationsCount () {
|
notificationsCount () {
|
||||||
return this.notifications.length;
|
const g1g1Count = this.shouldShowG1g1 ? 1 : 0;
|
||||||
|
return this.notifications.length + g1g1Count;
|
||||||
},
|
},
|
||||||
hasUnseenNotifications () {
|
hasUnseenNotifications () {
|
||||||
return this.notifications.some(notification => (notification.seen === false));
|
return this.notifications.some(notification => (notification.seen === false));
|
||||||
@@ -299,6 +307,30 @@ export default {
|
|||||||
showOnboardingGuide () {
|
showOnboardingGuide () {
|
||||||
return !hasCompletedOnboarding(this.user);
|
return !hasCompletedOnboarding(this.user);
|
||||||
},
|
},
|
||||||
|
currentG1g1Event () {
|
||||||
|
return find(this.currentEventList, event => event.promo === 'g1g1');
|
||||||
|
},
|
||||||
|
g1g1EventKey () {
|
||||||
|
if (!this.currentG1g1Event || !this.currentG1g1Event.start) return null;
|
||||||
|
const startDate = new Date(this.currentG1g1Event.start);
|
||||||
|
return `${startDate.getFullYear()}-${startDate.getMonth()}`;
|
||||||
|
},
|
||||||
|
shouldShowG1g1 () {
|
||||||
|
if (!this.currentG1g1Event) return false;
|
||||||
|
const eventKey = this.g1g1EventKey;
|
||||||
|
if (eventKey && window.sessionStorage.getItem(`hide-g1g1-${eventKey}`) === 'true') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !this.g1g1Hidden;
|
||||||
|
},
|
||||||
|
g1g1Notification () {
|
||||||
|
return {
|
||||||
|
type: 'GIFT_ONE_GET_ONE',
|
||||||
|
id: `g1g1-event-${this.currentG1g1Event?.start || 'default'}`,
|
||||||
|
data: {},
|
||||||
|
seen: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
const onboardingPanelState = getLocalSetting(CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE);
|
const onboardingPanelState = getLocalSetting(CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE);
|
||||||
@@ -364,6 +396,9 @@ export default {
|
|||||||
isActionable (notification) {
|
isActionable (notification) {
|
||||||
return this.actionableNotifications.indexOf(notification.type) !== -1;
|
return this.actionableNotifications.indexOf(notification.type) !== -1;
|
||||||
},
|
},
|
||||||
|
handleG1g1Removed () {
|
||||||
|
this.g1g1Hidden = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -176,7 +176,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
showProfile (startingPage) {
|
showProfile (startingPage) {
|
||||||
this.$router.push({ name: startingPage });
|
const userId = this.$store.state.user.data._id;
|
||||||
|
let path = `/profile/${userId}`;
|
||||||
|
if (startingPage !== 'profile') {
|
||||||
|
path += `#${startingPage}`;
|
||||||
|
}
|
||||||
|
this.$router.push(path);
|
||||||
},
|
},
|
||||||
toLearnMore () {
|
toLearnMore () {
|
||||||
this.$router.push({ name: 'subscription' });
|
this.$router.push({ name: 'subscription' });
|
||||||
|
|||||||
@@ -454,17 +454,14 @@ export default {
|
|||||||
},
|
},
|
||||||
isUserMentioned () {
|
isUserMentioned () {
|
||||||
const message = this.msg;
|
const message = this.msg;
|
||||||
|
|
||||||
if (message.highlight) {
|
if (message.highlight) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { user } = this;
|
const { user } = this;
|
||||||
const displayName = user.profile.name;
|
|
||||||
const { username } = user.auth.local;
|
const { username } = user.auth.local;
|
||||||
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
|
if (!username) return false;
|
||||||
message.highlight = new RegExp(pattern, 'i').test(message.text);
|
const usernamePattern = new RegExp(`@${escapeRegExp(username)}(?:\\b|(?=[^a-zA-Z0-9_]))`, 'i');
|
||||||
|
message.highlight = usernamePattern.test(message.text);
|
||||||
return message.highlight;
|
return message.highlight;
|
||||||
},
|
},
|
||||||
flagCountDescription () {
|
flagCountDescription () {
|
||||||
|
|||||||
@@ -851,7 +851,7 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.genericPurchase) {
|
if (this.genericPurchase) {
|
||||||
await this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy);
|
this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy);
|
||||||
await this.purchased(this.item.text);
|
await this.purchased(this.item.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-secondary d-flex align-items-center justify-content-center"
|
class="btn btn-secondary d-flex align-items-center justify-content-center"
|
||||||
:class="{'btn-disabled': !canSave}"
|
:class="{disabled: !canSave}"
|
||||||
type="button"
|
type="button"
|
||||||
@click="submit()"
|
@click="submit()"
|
||||||
>
|
>
|
||||||
@@ -162,13 +162,13 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="habit-option-icon svg-icon no-transition"
|
class="habit-option-icon svg-icon no-transition"
|
||||||
:class="task.up ? '' : 'icon-disabled'"
|
:class="task.up ? '' : 'disabled'"
|
||||||
v-html="icons.positive"
|
v-html="icons.positive"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="habit-option-label no-transition"
|
class="habit-option-label no-transition"
|
||||||
:class="task.up ? cssClass('icon') : 'label-disabled'"
|
:class="task.up ? cssClass('icon') : 'disabled'"
|
||||||
>
|
>
|
||||||
{{ $t('positive') }}
|
{{ $t('positive') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -188,13 +188,13 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="habit-option-icon no-transition svg-icon negative mx-auto"
|
class="habit-option-icon no-transition svg-icon negative mx-auto"
|
||||||
:class="task.down ? '' : 'icon-disabled'"
|
:class="task.down ? '' : 'disabled'"
|
||||||
v-html="icons.negative"
|
v-html="icons.negative"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="habit-option-label no-transition"
|
class="habit-option-label no-transition"
|
||||||
:class="task.down ? cssClass('icon') : 'label-disabled'"
|
:class="task.down ? cssClass('icon') : 'disabled'"
|
||||||
>
|
>
|
||||||
{{ $t('negative') }}
|
{{ $t('negative') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -592,7 +592,7 @@
|
|||||||
<button
|
<button
|
||||||
class="btn btn-primary btn-footer
|
class="btn btn-primary btn-footer
|
||||||
d-flex align-items-center justify-content-center"
|
d-flex align-items-center justify-content-center"
|
||||||
:class="{'btn-disabled': !canSave}"
|
:class="{disabled: !canSave}"
|
||||||
type="button"
|
type="button"
|
||||||
@click="submit()"
|
@click="submit()"
|
||||||
>
|
>
|
||||||
@@ -881,14 +881,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-disabled {
|
.disabled {
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
color: $gray-200;
|
color: $gray-200;
|
||||||
line-height: 1.714;
|
line-height: 1.714;
|
||||||
box-shadow: 0px 1px 3px 0px rgba(26, 24, 29, 0.12), 0px 1px 2px 0px rgba(26, 24, 29, 0.24);
|
box-shadow: 0px 1px 3px 0px rgba(26, 24, 29, 0.12), 0px 1px 2px 0px rgba(26, 24, 29, 0.24);
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.6;
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
@@ -950,7 +948,7 @@
|
|||||||
height: 10px;
|
height: 10px;
|
||||||
color: $white;
|
color: $white;
|
||||||
|
|
||||||
&.icon-disabled {
|
&.disabled {
|
||||||
color: $gray-200;
|
color: $gray-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -964,7 +962,7 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
&.label-disabled {
|
&.disabled {
|
||||||
color: $gray-100;
|
color: $gray-100;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
@@ -1020,7 +1018,7 @@
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group-outer.disabled .input-group-text {
|
.disabled .input-group-text {
|
||||||
color: $gray-200;
|
color: $gray-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
.toggle-switch-inner:before {
|
.toggle-switch-inner:before {
|
||||||
content: "";
|
content: "";
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
background-color: $green-50;
|
background-color: $green-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-switch-inner:after {
|
.toggle-switch-inner:after {
|
||||||
|
|||||||
@@ -1126,7 +1126,12 @@ export default {
|
|||||||
this.loadUser();
|
this.loadUser();
|
||||||
this.oldTitle = this.$store.state.title;
|
this.oldTitle = this.$store.state.title;
|
||||||
this.handleExternalLinks();
|
this.handleExternalLinks();
|
||||||
this.selectPage(this.startingPage);
|
// Check if there's a hash in the URL to determine the starting page
|
||||||
|
let pageToSelect = this.startingPage;
|
||||||
|
if (window.location.hash && (window.location.hash === '#stats' || window.location.hash === '#achievements')) {
|
||||||
|
pageToSelect = window.location.hash.substring(1);
|
||||||
|
}
|
||||||
|
this.selectPage(pageToSelect);
|
||||||
this.$root.$on('habitica:report-profile-result', () => {
|
this.$root.$on('habitica:report-profile-result', () => {
|
||||||
this.loadUser();
|
this.loadUser();
|
||||||
});
|
});
|
||||||
@@ -1211,10 +1216,15 @@ export default {
|
|||||||
},
|
},
|
||||||
selectPage (page) {
|
selectPage (page) {
|
||||||
this.selectedPage = page || 'profile';
|
this.selectedPage = page || 'profile';
|
||||||
window.history.replaceState(null, null, '');
|
const profileUserId = this.userId || this.userLoggedIn._id;
|
||||||
|
let newPath = `/profile/${profileUserId}`;
|
||||||
|
if (page !== 'profile') {
|
||||||
|
newPath += `#${page}`;
|
||||||
|
}
|
||||||
|
window.history.replaceState(null, null, newPath);
|
||||||
this.$store.dispatch('common:setTitle', {
|
this.$store.dispatch('common:setTitle', {
|
||||||
section: this.$t('user'),
|
section: this.$t('user'),
|
||||||
subSection: this.$t(this.startingPage),
|
subSection: this.$t(page),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getNextIncentive () {
|
getNextIncentive () {
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import habiticaMarkdown from 'habitica-markdown/withMentions';
|
import habiticaMarkdown from 'habitica-markdown/withMentions';
|
||||||
|
import escapeRegExp from 'lodash/escapeRegExp';
|
||||||
|
|
||||||
export default function renderWithMentions (text, user) {
|
export default function renderWithMentions (text, user) {
|
||||||
if (!text) return null;
|
if (!text) return null;
|
||||||
const env = { userName: user.auth.local.username, displayName: user.profile.name };
|
const env = { userName: user.auth.local.username };
|
||||||
return habiticaMarkdown.render(String(text), env);
|
let html = habiticaMarkdown.render(String(text), env);
|
||||||
|
|
||||||
|
if (user.auth.local.username) {
|
||||||
|
const username = escapeRegExp(user.auth.local.username);
|
||||||
|
const regex = new RegExp(`(<span class="at-text">@)(${username})(</span>)`, 'gi');
|
||||||
|
html = html.replace(regex, (match, p1, p2, p3) => `${p1.replace('at-text', 'at-text at-highlight')}${p2}${p3}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ const router = new VueRouter({
|
|||||||
path: '/profile/:userId',
|
path: '/profile/:userId',
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
|
{ name: 'profile', path: '/user/profile' },
|
||||||
|
{ name: 'stats', path: '/user/stats' },
|
||||||
|
{ name: 'achievements', path: '/user/achievements' },
|
||||||
{
|
{
|
||||||
path: '/inventory',
|
path: '/inventory',
|
||||||
component: InventoryContainer,
|
component: InventoryContainer,
|
||||||
@@ -369,6 +372,10 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
if (to.params.startingPage !== undefined) {
|
if (to.params.startingPage !== undefined) {
|
||||||
startingPage = to.params.startingPage;
|
startingPage = to.params.startingPage;
|
||||||
}
|
}
|
||||||
|
// Check if there's a hash in the URL for stats or achievements
|
||||||
|
if (to.hash === '#stats' || to.hash === '#achievements') {
|
||||||
|
startingPage = to.hash.substring(1);
|
||||||
|
}
|
||||||
if (from.name === null) {
|
if (from.name === null) {
|
||||||
store.state.postLoadModal = `profile/${to.params.userId}`;
|
store.state.postLoadModal = `profile/${to.params.userId}`;
|
||||||
return next({ name: 'tasks' });
|
return next({ name: 'tasks' });
|
||||||
@@ -389,10 +396,18 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((to.name === 'stats' || to.name === 'achievements' || to.name === 'profile') && from.name !== null) {
|
if ((to.name === 'stats' || to.name === 'achievements' || to.name === 'profile') && from.name !== null) {
|
||||||
|
const userId = store.state.user.data._id;
|
||||||
|
let redirectPath = `/profile/${userId}`;
|
||||||
|
if (to.name === 'stats') {
|
||||||
|
redirectPath += '#stats';
|
||||||
|
} else if (to.name === 'achievements') {
|
||||||
|
redirectPath += '#achievements';
|
||||||
|
}
|
||||||
router.app.$emit('habitica:show-profile', {
|
router.app.$emit('habitica:show-profile', {
|
||||||
|
userId,
|
||||||
startingPage: to.name,
|
startingPage: to.name,
|
||||||
fromPath: from.path,
|
fromPath: from.path,
|
||||||
toPath: to.path,
|
toPath: redirectPath,
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ describe('renderWithMentions', () => {
|
|||||||
expect(result).to.be.null;
|
expect(result).to.be.null;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('highlights displayname', () => {
|
test('does not highlight displayname to prevent impersonation', () => {
|
||||||
const text = 'hello @displayedUser with text after';
|
const text = 'hello @displayedUser with text after';
|
||||||
|
|
||||||
const result = renderMarkdown(text, user('user', 'displayedUser'));
|
const result = renderMarkdown(text, user('user', 'displayedUser'));
|
||||||
|
expect(result).to.contain('<span class="at-text">@displayedUser</span>');
|
||||||
expect(result).to.contain('<span class="at-text at-highlight">@displayedUser</span>');
|
expect(result).to.not.contain('<span class="at-text at-highlight">@displayedUser</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('highlights username', () => {
|
test('highlights username', () => {
|
||||||
@@ -56,7 +56,8 @@ describe('renderWithMentions', () => {
|
|||||||
|
|
||||||
const result = renderMarkdown(plainText, user('use', 'mentions'));
|
const result = renderMarkdown(plainText, user('use', 'mentions'));
|
||||||
|
|
||||||
expect(result).to.contain('<span class="at-text at-highlight">@mentions</span>');
|
expect(result).to.contain('<span class="at-text">@mentions</span>');
|
||||||
|
expect(result).to.not.contain('<span class="at-text at-highlight">@mentions</span>');
|
||||||
expect(result).to.contain('<span class="at-text at-highlight">@use</span>');
|
expect(result).to.contain('<span class="at-text at-highlight">@use</span>');
|
||||||
expect(result).to.contain('<span class="at-text">@mail</span>');
|
expect(result).to.contain('<span class="at-text">@mail</span>');
|
||||||
expect(result).to.not.contain('<span class="at-text at-highlight">@mentions</span>.com');
|
expect(result).to.not.contain('<span class="at-text at-highlight">@mentions</span>.com');
|
||||||
|
|||||||
@@ -3437,5 +3437,7 @@
|
|||||||
"shieldSpecialFall2025WarriorText": "Sasquatch Schild",
|
"shieldSpecialFall2025WarriorText": "Sasquatch Schild",
|
||||||
"shieldSpecialFall2025WarriorNotes": "Verschaffe dir etwas mehr Zeit zum Nachdenken und Planen, indem du dich vor deinen nächsten Tagesaufgaben abschirmst. Erhöht die Konstitution um <%= con %>. Limitierte Auflage Herbst 2025 Ausrüstung.",
|
"shieldSpecialFall2025WarriorNotes": "Verschaffe dir etwas mehr Zeit zum Nachdenken und Planen, indem du dich vor deinen nächsten Tagesaufgaben abschirmst. Erhöht die Konstitution um <%= con %>. Limitierte Auflage Herbst 2025 Ausrüstung.",
|
||||||
"shieldSpecialFall2025HealerNotes": "Verschaffe dir etwas mehr Zeit, um Vorräte zu sammeln, indem du dich vor deinen Aufgaben abschirmst. Erhöht die Konstitution um <%= con %>. Limitierte Ausgabe Herbst 2025 Ausrüstung.",
|
"shieldSpecialFall2025HealerNotes": "Verschaffe dir etwas mehr Zeit, um Vorräte zu sammeln, indem du dich vor deinen Aufgaben abschirmst. Erhöht die Konstitution um <%= con %>. Limitierte Ausgabe Herbst 2025 Ausrüstung.",
|
||||||
"shieldArmoireSoftOrangePillowNotes": "Der vorbereitete Krieger packt für jede Expedition ein Kissen ein. Mach dich bereit, neue Verpflichtungen zu übernehmen ... sogar während du ein Nickerchen machst. Erhöht Intelligenz und Wahrnehmung um jeweils <%= attrs %>. Verzauberter Kleiderschrank: Orangenes Loungewear-Set (Gegenstand 3 von 3)."
|
"shieldArmoireSoftOrangePillowNotes": "Der vorbereitete Krieger packt für jede Expedition ein Kissen ein. Mach dich bereit, neue Verpflichtungen zu übernehmen ... sogar während du ein Nickerchen machst. Erhöht Intelligenz und Wahrnehmung um jeweils <%= attrs %>. Verzauberter Kleiderschrank: Orangenes Loungewear-Set (Gegenstand 3 von 3).",
|
||||||
|
"bodyMystery202509Text": "Schal des windgepeitschten Wanderers",
|
||||||
|
"armorSpecialFall2025RogueNotes": "Ein hartes und schmales Ziel in dieser saisonalen Rüstung ist am schwersten zu treffen. Erhöht die Wahrnehmung um <%= per %>. Limitierte Ausgabe Herbst 2025 Ausrüstung."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"stable": "Haus- und Reittiere",
|
"stable": "Haustiere und Reittiere",
|
||||||
"pets": "Haustiere",
|
"pets": "Haustiere",
|
||||||
"activePet": "Aktives Haustier",
|
"activePet": "Aktives Haustier",
|
||||||
"noActivePet": "Kein aktives Haustier",
|
"noActivePet": "Kein aktives Haustier",
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
"awardWinners": "Award Winner",
|
"awardWinners": "Award Winner",
|
||||||
"doYouWantedToDeleteChallenge": "Do you want to delete this Challenge?",
|
"doYouWantedToDeleteChallenge": "Do you want to delete this Challenge?",
|
||||||
"deleteChallenge": "Delete Challenge",
|
"deleteChallenge": "Delete Challenge",
|
||||||
|
"deleteChallengeRefundDescription": "If you delete this Challenge, you will be refunded the Gem prize and the Challenge tasks will remain on the participants' task boards.",
|
||||||
"challengeNamePlaceholder": "What is your Challenge name?",
|
"challengeNamePlaceholder": "What is your Challenge name?",
|
||||||
"challengeSummary": "Summary",
|
"challengeSummary": "Summary",
|
||||||
"challengeSummaryPlaceholder": "Write a short description advertising your Challenge to other Habiticans. What is the main purpose of your Challenge and why should people join it? Try to include useful keywords in the description so that Habiticans can easily find it when they search!",
|
"challengeSummaryPlaceholder": "Write a short description advertising your Challenge to other Habiticans. What is the main purpose of your Challenge and why should people join it? Try to include useful keywords in the description so that Habiticans can easily find it when they search!",
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
"passwordReset": "If we have your email or username on file, instructions for setting a new password have been sent to your email.",
|
"passwordReset": "If we have your email or username on file, instructions for setting a new password have been sent to your email.",
|
||||||
"invalidLoginCredentialsLong": "Your email, username, or password are incorrect. Please try again or use \"Forgot Password.\"",
|
"invalidLoginCredentialsLong": "Your email, username, or password are incorrect. Please try again or use \"Forgot Password.\"",
|
||||||
"invalidCredentials": "There is no account that uses those credentials.",
|
"invalidCredentials": "There is no account that uses those credentials.",
|
||||||
"accountSuspended": "Your account @<%= username %> has been blocked. For additional information, or to request an appeal, email admin@habitica.com with your Habitica username or User ID.",
|
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the Community Guidelines (https://habitica.com/static/community-guidelines) or Terms of Service (https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please include your @Username in the email.",
|
||||||
"accountSuspendedTitle": "Account has been suspended",
|
"accountSuspendedTitle": "Account has been suspended",
|
||||||
"unsupportedNetwork": "This network is not currently supported.",
|
"unsupportedNetwork": "This network is not currently supported.",
|
||||||
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
"notEnoughGems": "Not enough Gems",
|
"notEnoughGems": "Not enough Gems",
|
||||||
"alreadyHave": "Whoops! You already have this item. No need to buy it again!",
|
"alreadyHave": "Whoops! You already have this item. No need to buy it again!",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
|
"gem": "Gem",
|
||||||
"gems": "Gems",
|
"gems": "Gems",
|
||||||
"needMoreGems": "Need More Gems?",
|
"needMoreGems": "Need More Gems?",
|
||||||
"needMoreGemsInfo": "Purchase Gems now, or become a subscriber to buy Gems with Gold, get monthly mystery items, enjoy increased drop caps and more!",
|
"needMoreGemsInfo": "Purchase Gems now, or become a subscriber to buy Gems with Gold, get monthly mystery items, enjoy increased drop caps and more!",
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
"needsTextPlaceholder": "Type your message here.",
|
"needsTextPlaceholder": "Type your message here.",
|
||||||
"messageCopiedToClipboard": "Message copied to clipboard.",
|
"messageCopiedToClipboard": "Message copied to clipboard.",
|
||||||
"leaderOnlyChallenges": "Only group leader can create challenges",
|
"leaderOnlyChallenges": "Only group leader can create challenges",
|
||||||
"sendGift": "Send a Gift",
|
"sendGift": "Send Gift",
|
||||||
"selectGift": "Select Gift",
|
"selectGift": "Select Gift",
|
||||||
"selectSubscription": "Select Subscription",
|
"selectSubscription": "Select Subscription",
|
||||||
"sendGiftToWhom": "Who would you like to send a gift to?",
|
"sendGiftToWhom": "Who would you like to send a gift to?",
|
||||||
|
|||||||
@@ -3389,7 +3389,7 @@
|
|||||||
"weaponSpecialFall2025MageNotes": "Una poderosa arma capaz de trazar una senda segura a través de los terrores del Bosque Negro. Aumenta la Inteligencia en <%= int %> y la Percepción en <%= per %>. Equipamiento de Edición Limitada Otoño 2025.",
|
"weaponSpecialFall2025MageNotes": "Una poderosa arma capaz de trazar una senda segura a través de los terrores del Bosque Negro. Aumenta la Inteligencia en <%= int %> y la Percepción en <%= per %>. Equipamiento de Edición Limitada Otoño 2025.",
|
||||||
"weaponSpecialFall2025WarriorText": "Hacha de Bigfoot",
|
"weaponSpecialFall2025WarriorText": "Hacha de Bigfoot",
|
||||||
"weaponSpecialFall2025HealerText": "Hacha Kobold",
|
"weaponSpecialFall2025HealerText": "Hacha Kobold",
|
||||||
"weaponSpecialFall2025HealerNotes": "Una poderosa arma capaz de trazar una senda segura a través de los obstáculos del Bosque Negro. Aumenta la Fuerza en <%= str %>. Equipamiento de Edición Limitada Otoño 2025.",
|
"weaponSpecialFall2025HealerNotes": "Una poderosa arma capaz de trazar una senda segura a través de los obstáculos del Bosque Negro. Aumenta la Inteligencia en <%= int %>. Equipamiento de Edición Limitada Otoño 2025.",
|
||||||
"weaponSpecialFall2025MageText": "Hacha de Fantasma Enmascarado",
|
"weaponSpecialFall2025MageText": "Hacha de Fantasma Enmascarado",
|
||||||
"weaponMystery202511Text": "Espada Escarcha",
|
"weaponMystery202511Text": "Espada Escarcha",
|
||||||
"weaponMystery202511Notes": "El halo helado de esta espada te permitirá realizar con rapidez incluso las tareas rojas más oscuras. No otorga ningún beneficio. Artículo de Suscriptor Noviembre 2025.",
|
"weaponMystery202511Notes": "El halo helado de esta espada te permitirá realizar con rapidez incluso las tareas rojas más oscuras. No otorga ningún beneficio. Artículo de Suscriptor Noviembre 2025.",
|
||||||
|
|||||||
@@ -227,5 +227,12 @@
|
|||||||
"subscriptionDetail24": "有料会員が「タイムトラベラーズショップ」からアイテムを収集できる機会を、年間4回以上に増やしたいと考えました。",
|
"subscriptionDetail24": "有料会員が「タイムトラベラーズショップ」からアイテムを収集できる機会を、年間4回以上に増やしたいと考えました。",
|
||||||
"subscriptionHeading3": "リリース当日特典",
|
"subscriptionHeading3": "リリース当日特典",
|
||||||
"subscriptionPara1": "新しいスケジュールへの移行をスムーズにするため、既存の有料会員にはリリース当日に追加の特典が用意されます。この変更に伴い、引き続きサポートしてくださる皆様に心から感謝いたします!",
|
"subscriptionPara1": "新しいスケジュールへの移行をスムーズにするため、既存の有料会員にはリリース当日に追加の特典が用意されます。この変更に伴い、引き続きサポートしてくださる皆様に心から感謝いたします!",
|
||||||
"subscriptionDetail4400": "現在、毎月ジェムを<%= initialNumber %>個アンロックしている場合、ジェムの上限は<%= roundedNumber %>個に調整されます。"
|
"subscriptionDetail4400": "現在、毎月ジェムを<%= initialNumber %>個アンロックしている場合、ジェムの上限は<%= roundedNumber %>個に調整されます。",
|
||||||
|
"subscriptionDetail48": "ミステリー装備セットなど、他の有料プランの特典には変更ありますか?",
|
||||||
|
"subscriptionDetail33": "このご褒美は、11月19日以前に有料プランを始めたアカウントしか受け取れません。",
|
||||||
|
"subscriptionDetail42": "有料プランに登録している間、1ヶ月間ログインしない場合、この特典はまだ受け取れますか?",
|
||||||
|
"subscriptionDetail400": "現在の有料会員には、リリース後の最初の月の初回ログイン時に、最初のミスティック砂時計と毎月のジェム上限が+2増加します。つまり、もしすでに11月にログインしている場合、最初の定期的な増加は12月に行われます。",
|
||||||
|
"subscriptionDetail40": "私は有料会員ですが、新しいスケジュールでは最初の定期的な神秘の砂時計とジェム上限の増加はいつ受け取れますか?",
|
||||||
|
"subscriptionDetail420": "ミステリーギアセットと同様に、登録している間にログインしなくても神秘の砂時計やジェムの上限アップを見逃すことはありません。次回ログインしたときに、登録していた期間のすべての特典を受け取ることができます。",
|
||||||
|
"subscriptionDetail43": "有料プランを申し込んでからキャンセルした場合でも、特典は受けられますか?"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3185,5 +3185,7 @@
|
|||||||
"shieldSpecialFall2025HealerText": "コボルドの盾",
|
"shieldSpecialFall2025HealerText": "コボルドの盾",
|
||||||
"shieldArmoireHattersPocketWatchText": "ピカピカなポケットウォッチ",
|
"shieldArmoireHattersPocketWatchText": "ピカピカなポケットウォッチ",
|
||||||
"shieldSpecialSummer2024HealerText": "海貝の盾",
|
"shieldSpecialSummer2024HealerText": "海貝の盾",
|
||||||
"shieldSpecialSummer2024HealerNotes": "このぴかぴかする盾は、海貝の杖よりも強いです。体質が<%= con %>上がります。2024年夏の限定装備。"
|
"shieldSpecialSummer2024HealerNotes": "このぴかぴかする盾は、海貝の杖よりも強いです。体質が<%= con %>上がります。2024年夏の限定装備。",
|
||||||
|
"armorMystery202504Notes": "「汚れた雪男」、だって?本当は可愛らしいでしょ!効果なし。2025年4月の有料会員アイテム。",
|
||||||
|
"armorArmoireSpringPetalYukataText": "春の花びらの浴衣"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
"questEvilSantaDropBearCubPolarMount": "シロクマ(乗騎)",
|
"questEvilSantaDropBearCubPolarMount": "シロクマ(乗騎)",
|
||||||
"questEvilSanta2Text": "子グマの捜索",
|
"questEvilSanta2Text": "子グマの捜索",
|
||||||
"questEvilSanta2Notes": "猟師のサンタが乗騎のシロクマを捕まえたとき、子グマは、氷原に逃げていきました。たしかに森の中の氷の結晶の音に交じって、小枝をふむ音や雪がくだける音が聞こえます。足あとだ!それを追いかけようと走り出します。足あとと折れた小枝を見のがしてはいけません。子グマを見つけ出しましょう!<br><br><strong>注意</strong>:「子グマの捜索」のクエストは何回でも挑戦できますが、クエスト報酬の特別なペットが手に入るのは最初の一回だけです。",
|
"questEvilSanta2Notes": "猟師のサンタが乗騎のシロクマを捕まえたとき、子グマは、氷原に逃げていきました。たしかに森の中の氷の結晶の音に交じって、小枝をふむ音や雪がくだける音が聞こえます。足あとだ!それを追いかけようと走り出します。足あとと折れた小枝を見のがしてはいけません。子グマを見つけ出しましょう!<br><br><strong>注意</strong>:「子グマの捜索」のクエストは何回でも挑戦できますが、クエスト報酬の特別なペットが手に入るのは最初の一回だけです。",
|
||||||
"questEvilSanta2Completion": "子グマを見つけました! ずっとあなたから離れないでしょう。",
|
"questEvilSanta2Completion": "子グマを見つけました!ずっとあなたから離れないでしょう。",
|
||||||
"questEvilSanta2CollectTracks": "足跡",
|
"questEvilSanta2CollectTracks": "足跡",
|
||||||
"questEvilSanta2CollectBranches": "折れた小枝",
|
"questEvilSanta2CollectBranches": "折れた小枝",
|
||||||
"questEvilSanta2DropBearCubPolarPet": "シロクマ(ペット)",
|
"questEvilSanta2DropBearCubPolarPet": "シロクマ(ペット)",
|
||||||
"questGryphonText": "炎のグリフォン",
|
"questGryphonText": "炎のグリフォン",
|
||||||
"questGryphonNotes": "偉大な猛獣使い、<strong>baconsaur</strong>があなたのパーティーに助けを求めてきました。「冒険者よ、どうか私を助けてください! 大切なグリフォンが逃げてしまい、Habit シティーを自由気ままに飛び回り、人びと に恐怖を与えているのです。もしグリフォンを止められたら、お礼にグリフォンのたまごを差し上げます!」",
|
"questGryphonNotes": "偉大な猛獣使い、<strong>baconsaur</strong>があなたのパーティーに助けを求めてきました。「冒険者よ、どうか私を助けてください! 大切なグリフォンが逃げてしまい、Habit シティーを自由気ままに飛び回り、人びと に恐怖を与えているのです。もしグリフォンを止められたら、お礼にグリフォンのたまごを差し上げます!」",
|
||||||
"questGryphonCompletion": "やりました! 強い獣はこそこそとはずかしそうに主人の元に帰りました。「驚いた! 冒険者よ、よくやってくれましたね!」 <strong>baconsaur</strong> は声を上げました。 「どうかこのグリフォンのたまごを受けとってください。あなたならきっとうまく育てられるでしょう!」",
|
"questGryphonCompletion": "やりました!強い獣はこそこそとはずかしそうに主人の元に帰ります。「驚いた!冒険者よ、よくやってくれましたね!」<strong>baconsaur</strong> は声を上げます。 「どうかこのグリフォンのたまごを受けとってください。あなたならきっとうまく育てられるでしょう!」",
|
||||||
"questGryphonBoss": "炎のグリフォン",
|
"questGryphonBoss": "炎のグリフォン",
|
||||||
"questGryphonDropGryphonEgg": "グリフォン ( たまご )",
|
"questGryphonDropGryphonEgg": "グリフォン ( たまご )",
|
||||||
"questGryphonUnlockText": "市場でグリフォンのたまごを買えるようになります",
|
"questGryphonUnlockText": "市場でグリフォンのたまごを買えるようになります",
|
||||||
@@ -815,5 +815,8 @@
|
|||||||
"questChameleonNotes": "タスクの森の暖かく雨の降る一角で、美しい一日が始まります。あなたは葉のコレクションに新しい蒐集物を探していると、目の前の枝が予告なしに色を変えました!しかも、その枝が動いたのです!<br><br>後ろにひっくり返りそうになりながら気づくと、それは枝ではなく巨大なカメレオンでした。体のあらゆる部分が色を変え続け、目はあちこちにキョロキョロと動いています。<br><br>「大丈夫ですか?」とあなたはカメレオンに尋ねます。<br><br>「うーん、ええと…」と、少しあわてた様子で彼は答えます。「隠れようとしているんだけど…色が次々に現れては消えるから圧倒されちゃって…ひとつに集中するのが難しいんだ…」<br><br>「なるほど」とあなた。「それなら手伝えるかも。小さなチャレンジで集中力を鍛えよう!色の準備はいい?」<br><br>「任せて!」とカメレオンは答えました。",
|
"questChameleonNotes": "タスクの森の暖かく雨の降る一角で、美しい一日が始まります。あなたは葉のコレクションに新しい蒐集物を探していると、目の前の枝が予告なしに色を変えました!しかも、その枝が動いたのです!<br><br>後ろにひっくり返りそうになりながら気づくと、それは枝ではなく巨大なカメレオンでした。体のあらゆる部分が色を変え続け、目はあちこちにキョロキョロと動いています。<br><br>「大丈夫ですか?」とあなたはカメレオンに尋ねます。<br><br>「うーん、ええと…」と、少しあわてた様子で彼は答えます。「隠れようとしているんだけど…色が次々に現れては消えるから圧倒されちゃって…ひとつに集中するのが難しいんだ…」<br><br>「なるほど」とあなた。「それなら手伝えるかも。小さなチャレンジで集中力を鍛えよう!色の準備はいい?」<br><br>「任せて!」とカメレオンは答えました。",
|
||||||
"questPlatypusRageEffect": "完璧主義者のカモノハシは川に潜って、パーティーに水をかけます!パーティーのマナが減ってしまいました!",
|
"questPlatypusRageEffect": "完璧主義者のカモノハシは川に潜って、パーティーに水をかけます!パーティーのマナが減ってしまいました!",
|
||||||
"questOpalCollectLibraRunes": "てんびん座のルーン",
|
"questOpalCollectLibraRunes": "てんびん座のルーン",
|
||||||
"questOpalCollectMercuryRunes": "水星のルーン"
|
"questOpalCollectMercuryRunes": "水星のルーン",
|
||||||
|
"questPlatypusNotes": "コンクエスト入り江の天気は晴れているが、宿題プリントのせいで台無しになっています。なんでいつも冒険したいときに宿題があるんだろう、とあなたは考えます。河川の生態系についての問題を解いていると、作文が出てきます。<br><br>「動物は川に住むためにどのように工夫しているか?知らんよ...」<br><br>どこから始めたらいいのかもわからず戸惑っていると、下流からバタバタする音が聞こえてきます。<br><br>「やれやれ」と、水面のしたからため息。すると、疲れたカモノハシが浮かび上がります。「全然巣穴が進まない。どうしても思い通りに作れないわ。」彼女は再び水中に潜り込み、幅広い尾が水面にあたるとあなたに大きな水しぶきがかかります。<br><br>「ちょっと待って、全部崩さないで!」とあなたは叫びます。早く助けてあげないと!(ついでに作文のアイデアが出るかも!)",
|
||||||
|
"questCatRageTitle": "怒りの猫パンチ",
|
||||||
|
"questRaccoonRageEffect": "欲張りなアライグマはあなたが救出したアイテムを奪い取り、木の中に詰め込みます。ボスは体力を30%回復してしまいました!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,5 +268,6 @@
|
|||||||
"earn2Gems": "登録している間、毎月<strong>+2個のジェム</strong>を獲得",
|
"earn2Gems": "登録している間、毎月<strong>+2個のジェム</strong>を獲得",
|
||||||
"mysterySet202502": "親切なアルレッキーノセット",
|
"mysterySet202502": "親切なアルレッキーノセット",
|
||||||
"maxGemCapGift": "受取人は<strong>最大限のジェム</strong>をゲットします",
|
"maxGemCapGift": "受取人は<strong>最大限のジェム</strong>をゲットします",
|
||||||
"subscribeAgainContinueHourglasses": "神秘の砂時計をゲットし続けるには、再び有料プランに登録してください"
|
"subscribeAgainContinueHourglasses": "神秘の砂時計をゲットし続けるには、再び有料プランに登録してください",
|
||||||
|
"immediate12Hourglasses": "初めて12ヶ月間の有料プランに登録したとき、<strong>12個の神秘の砂時計</strong> をすぐにゲットしよう!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -773,5 +773,7 @@
|
|||||||
"backgroundInsideACrystalNotes": "Kijk vanuit een Kristal naar buiten.",
|
"backgroundInsideACrystalNotes": "Kijk vanuit een Kristal naar buiten.",
|
||||||
"backgroundSnowyVillageText": "Besneeuwd Dorp",
|
"backgroundSnowyVillageText": "Besneeuwd Dorp",
|
||||||
"backgroundSnowyVillageNotes": "Bewonder een Besneeuwd Dorp.",
|
"backgroundSnowyVillageNotes": "Bewonder een Besneeuwd Dorp.",
|
||||||
"backgrounds122022": "SET 103: Uitgebracht december 2022"
|
"backgrounds122022": "SET 103: Uitgebracht december 2022",
|
||||||
|
"backgroundSpringtimeShowerText": "Lentedouche",
|
||||||
|
"backgroundSpringtimeShowerNotes": "Zie een bloemrijke lentedouche."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1772,7 +1772,7 @@
|
|||||||
"eyewearArmoireComedyMaskText": "Komediowa Maska",
|
"eyewearArmoireComedyMaskText": "Komediowa Maska",
|
||||||
"eyewearArmoireJewelersEyeLoupeText": "Lupa Jubilerska",
|
"eyewearArmoireJewelersEyeLoupeText": "Lupa Jubilerska",
|
||||||
"moreArmoireGearAvailable": "Do tego czasu, zostało do znalezienia <%= armoireCount %> części wyposażenia w Zaczarowanej Skrzyni!",
|
"moreArmoireGearAvailable": "Do tego czasu, zostało do znalezienia <%= armoireCount %> części wyposażenia w Zaczarowanej Skrzyni!",
|
||||||
"gearItemsCompleted": "Jesteś w posiadaniu całego ekwipunku klasy <%= class %>! Nowy ekwipunek jest wydawany podczas Gal sezonowych.",
|
"gearItemsCompleted": "Jesteś w posiadaniu całego ekwipunku klasy <%= klass %>! Nowy ekwipunek jest wydawany podczas Gal sezonowych.",
|
||||||
"moreArmoireGearComing": "Zaczarowana Skrzynia również comiesięcznie otrzymuje nowy asortyment!",
|
"moreArmoireGearComing": "Zaczarowana Skrzynia również comiesięcznie otrzymuje nowy asortyment!",
|
||||||
"weaponSpecialFall2019MageNotes": "Niezależnie od tego, czy chodzi o wykuwanie piorunów, wznoszenie fortyfikacji, czy po prostu wzbudzanie przerażenia w sercach śmiertelników, ta laska daje moc gigantów do czynienia cudów. Zwiększa inteligencję o <%= int %> i percepcję o <%= per %>. Limitowana edycja 2019 Jesienne Wyposażenie.",
|
"weaponSpecialFall2019MageNotes": "Niezależnie od tego, czy chodzi o wykuwanie piorunów, wznoszenie fortyfikacji, czy po prostu wzbudzanie przerażenia w sercach śmiertelników, ta laska daje moc gigantów do czynienia cudów. Zwiększa inteligencję o <%= int %> i percepcję o <%= per %>. Limitowana edycja 2019 Jesienne Wyposażenie.",
|
||||||
"weaponSpecialFall2019HealerText": "Przerażające Filakterium",
|
"weaponSpecialFall2019HealerText": "Przerażające Filakterium",
|
||||||
|
|||||||
@@ -130,7 +130,7 @@
|
|||||||
"optOutOfClasses": "Отказаться",
|
"optOutOfClasses": "Отказаться",
|
||||||
"chooseClass": "Выберите свой класс",
|
"chooseClass": "Выберите свой класс",
|
||||||
"chooseClassLearnMarkdown": "[Узнать больше о системе классов в стране Habitica](https://habitica.fandom.com/ru/wiki/Система_классов)",
|
"chooseClassLearnMarkdown": "[Узнать больше о системе классов в стране Habitica](https://habitica.fandom.com/ru/wiki/Система_классов)",
|
||||||
"optOutOfClassesText": "Не хотите утруждать себя выбором класса? Или предпочитаете определиться позже? Отказавшись от класса, вы останетесь простым воином без специальных способностей. Вы также можете прочитать про систему классов на нашей вики позже и включить классы в любое время на странице Пользователь -> Настройки.",
|
"optOutOfClassesText": "Пока не готовы выбрать? Это не к спеху. Если откажетесь, вы можете ознакомиться с каждым классом в <a href='/static/faq#what-classes' target='_blank'>нашем FAQ</a> и по готовности зайти в «Настройки» для выбора класса.",
|
||||||
"selectClass": "Выбрать <%= heroClass %>",
|
"selectClass": "Выбрать <%= heroClass %>",
|
||||||
"select": "Выбрать",
|
"select": "Выбрать",
|
||||||
"stealth": "Хитрость",
|
"stealth": "Хитрость",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"webFaqStillNeedHelp": "如果您有任何疑問,但沒出現在以上列表中或是 [Wiki FAQ](https://habitica.fandom.com/wiki/FAQ)中,請至 [Habitica Help guild](https://habitica.com/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a)裡詢問,我們相當樂意協助。",
|
"webFaqStillNeedHelp": "如果您有任何疑問,但沒出現在以上列表中或是 [Wiki FAQ](https://habitica.fandom.com/wiki/FAQ)中,請至 [Habitica Help guild](https://habitica.com/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a)裡詢問,我們相當樂意協助。",
|
||||||
"commonQuestions": "常見問題",
|
"commonQuestions": "常見問題",
|
||||||
"faqQuestion25": "這些不同的任務類型是什麼?",
|
"faqQuestion25": "這些不同的任務類型是什麼?",
|
||||||
"webFaqAnswer25": "習慣可以用於你希望每天做多次的事情,或是一些不確定的計劃。 它們可以被選擇完成或完不成,選擇完成會帶來金幣和經驗的獎勵,而另一種則會對你造成生命值傷害。\n\n每日任務是你希望更嚴謹地按計畫進行的重複任務,例如每天一次、每週三次或是每月四次。 錯過完成每日任務會導致你受到生命值傷害,但這些任務越艱難,帶來的獎勵就越豐厚!\n\n待辦事項是一次性任務,當你完成時會提供獎勵。 待辦事項可以設定截止時間,但你不會因為錯過時間而失去生命值。\n\n選擇最適合你想要完成的任務類型吧!",
|
"webFaqAnswer25": "Habitica 使用三種不同的任務類型來滿足您的需求,分別是:習慣 (Habits)、每日任務 (Dailies),以及待辦事項 (To-Do's)。\n\n習慣 (Habits)\n「習慣」分為正面與負面兩種,適合用來追蹤那些您想在一天內多次執行、或沒有固定時程的行為。正面的習慣會為您帶來獎勵(如金幣和經驗值),而負面的習慣則會讓您失去生命值。\n\n每日任務 (Dailies)\n「每日任務」是您想要在更有結構的時程上完成的重複性任務。例如,每天一次、每週三次,或是一個月四次。若未完成每日任務會導致您失去生命值,但任務的難度越高,完成後的獎勵也越豐厚!\n\n待辦事項 (To-Do’s)\n「待辦事項」是一次性的任務,在您完成後會提供獎勵。待辦事項可以設定截止日期,但即使錯過了,您也不會因此失去生命值。\n\n請挑選最適合您想達成目標的任務類型吧!",
|
||||||
"faqQuestion26": "什麼是範例任務?",
|
"faqQuestion26": "什麼是範例任務?",
|
||||||
"contentAnswer62": "情人節魔術孵化藥水現在已納入每月的時間表中。",
|
"contentAnswer62": "情人節魔術孵化藥水現在已納入每月的時間表中。",
|
||||||
"contentFaqPara3": "如果您有任何上述答案未涵蓋的問題,您可以隨時透過 <%= mailto %> 聯絡我們的團隊!我們對於新內容發行計畫感到非常興奮,並期待未來有更多的計畫能幫助所有玩家讓 Habitica 變得更好。",
|
"contentFaqPara3": "如果您有任何上述答案未涵蓋的問題,您可以隨時透過 <%= mailto %> 聯絡我們的團隊!我們對於新內容發行計畫感到非常興奮,並期待未來有更多的計畫能幫助所有玩家讓 Habitica 變得更好。",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"subscriptionHeading2": "我們為什麼要做這些改變?",
|
"subscriptionHeading2": "我們為什麼要做這些改變?",
|
||||||
"subscriptionDetail20": "在目前的架構下,您很難了解您會收到多少個神秘沙漏,以及何時會收到。",
|
"subscriptionDetail20": "在目前的架構下,您很難了解您會收到多少個神秘沙漏,以及何時會收到。",
|
||||||
"faqQuestion27": "為什麼任務會改變顏色?",
|
"faqQuestion27": "為什麼任務會改變顏色?",
|
||||||
"faqQuestion28": "如果我需要休息,可以暫停每日任務嗎?",
|
"faqQuestion28": "如果我需要休息一下,可以暫停我的每日任務嗎?",
|
||||||
"faqQuestion29": "如何恢復 HP(生命值)?",
|
"faqQuestion29": "如何恢復 HP(生命值)?",
|
||||||
"faqQuestion30": "HP(生命值)歸零時會發生什麼事?",
|
"faqQuestion30": "HP(生命值)歸零時會發生什麼事?",
|
||||||
"faqQuestion32": "要怎麼選擇職業?",
|
"faqQuestion32": "要怎麼選擇職業?",
|
||||||
@@ -64,8 +64,8 @@
|
|||||||
"sunsetFaqHeader12": "公會銀行寶石會如何?",
|
"sunsetFaqHeader12": "公會銀行寶石會如何?",
|
||||||
"sunsetFaqPara21": "公會銀行中的寶石將在 8 月 8 日公會服務結束時退還給公會領袖。",
|
"sunsetFaqPara21": "公會銀行中的寶石將在 8 月 8 日公會服務結束時退還給公會領袖。",
|
||||||
"sunsetFaqHeader2": "為什麼酒館和公會服務要結束?",
|
"sunsetFaqHeader2": "為什麼酒館和公會服務要結束?",
|
||||||
"webFaqAnswer26": "正向習慣(你想要培養的行為;應該有「+」按鈕)\n\n * 吃維他命\n * 使用牙線\n * 學習一小時\n\n負向習慣(你想要限制或避免的行為;應該有「-」按鈕)\n\n * 抽菸\n * 無止盡滑手機\n * 咬指甲\n\n雙向習慣(同時包含正面與負面選項的行為;應有「+」與「-」按鈕)\n\n * 喝水 vs. 喝汽水\n * 學習 vs. 拖延\n\n每日任務範例(你想定期執行的事情)\n * 洗碗\n * 澆花\n * 30 分鐘的運動\n\n待辦事項範例(只需要完成一次的事情)\n\n * 預約時間\n * 整理衣櫃\n * 完成報告",
|
"webFaqAnswer26": "正面習慣 (您想要鼓勵的行為;應帶有「+」按鈕)\n\n * 吃維他命\n * 使用牙線\n * 學習一小時\n\n負向習慣(你想要限制或避免的行為;應該有「-」按鈕)\n\n * 抽菸\n * 無止盡滑手機\n * 咬指甲\n\n雙向習慣(同時包含正面與負面選項的行為;應同時帶有「+」與「-」按鈕)\n\n * 喝水 vs. 喝汽水\n * 學習 vs. 拖延\n\n每日任務範例 (您想依循規律時程重複的任務)\n * 洗碗\n * 澆花\n * 30 分鐘的體能運動\n\n待辦事項範例(您只需要完成一次的事情)\n\n * 預約時間\n * 整理衣櫃\n * 完成報告",
|
||||||
"webFaqAnswer27": "任務的顏色能夠一目了然地看出其價值。所有任務的初始顏色都是黃色(代表中性)、藍色(代表較好)、紅色(代表較差)。以下是決定每種任務類型價值的方式:\n\n習慣的顏色會根據你是按下「+」或「-」按鈕而變藍或變紅。如果沒完成某個正向習慣和負向習慣,久了它們就會逐漸變成黃色;雙向習慣的顏色則只會根據你按下的按鈕而改變。\n\n每日任務的顏色會根據完成頻率而變化,完成的每日任務顏色會變藍,未完成的每日任務顏色會變紅。\n\n待辦事項的顏色會隨著未完成時間的延長而逐漸變紅。\n\n任務的紅色愈深,完成它獲得的金幣和經驗值就愈多,所以即使是最艱鉅的任務,也一定要挑戰!",
|
"webFaqAnswer27": "任務的顏色直觀地呈現了該任務的價值。所有任務的初始顏色都是都是代表中性的黃色,藍色代表良好,紅色則代表不良。以下是各任務類型決定其顏色的方式:\n\n習慣會根據您點擊「+」或「-」按鈕而變得更藍或更紅。如果您長時間沒有點擊,正面與負面的習慣都會隨著時間逐漸退回至黃色。而雙向習慣的顏色則只會依據您的點擊而改變。\n\n每日任務的顏色會根據完成頻率而變化,完成的每日任務顏色會變藍,未完成的每日任務顏色會變紅。\n\n待辦事項在未完成的狀態下放置越久,顏色就會逐漸變得越紅。\n\n任務的紅色愈深,完成它獲得的金幣和經驗值就愈多,所以,請務必去挑戰那些您最艱困的任務!",
|
||||||
"webFaqAnswer28": "當然可以! 在「設定」中有個「暫停傷害」按鈕,可以防止你因為沒完成每日任務而損失生命值。如果你正在度假、需要休息,或因為其他原因需要暫停一下,這個功能可以幫得上忙。如果你正在打某個副本,你自己的待結算進度會暫停,但你仍然會因隊友沒完成每日任務而受到傷害。\n\n如果要暫停特定的每日任務,你可以編輯排程,改為「每 0 天到期一次」,直到你準備好重新開始為止。",
|
"webFaqAnswer28": "當然可以! 在「設定」中有個「暫停傷害」按鈕,可以防止你因為沒完成每日任務而損失生命值。如果你正在度假、需要休息,或因為其他原因需要暫停一下,這個功能可以幫得上忙。如果你正在打某個副本,你自己的待結算進度會暫停,但你仍然會因隊友沒完成每日任務而受到傷害。\n\n如果要暫停特定的每日任務,你可以編輯排程,改為「每 0 天到期一次」,直到你準備好重新開始為止。",
|
||||||
"webFaqAnswer29": "你可以從「獎勵」欄花費 25 金幣購買「治療藥水」,即可恢復 15 點生命值。另外,每次升級後都會重新滿血!",
|
"webFaqAnswer29": "你可以從「獎勵」欄花費 25 金幣購買「治療藥水」,即可恢復 15 點生命值。另外,每次升級後都會重新滿血!",
|
||||||
"webFaqAnswer30": "如果 HP(生命值)歸零,你就會降級一等、失去所有金幣,以及一件可以重新購買的裝備。",
|
"webFaqAnswer30": "如果 HP(生命值)歸零,你就會降級一等、失去所有金幣,以及一件可以重新購買的裝備。",
|
||||||
@@ -118,5 +118,19 @@
|
|||||||
"webFaqAnswer59": "Habitica團隊計劃提供了一種共享體驗,允許隊員輕鬆地在共享任務板上添加、分配和完成任務。憑借成員角色、狀態視圖和任務分配等功能,團隊計劃非常適合擁有共同目標的家庭或同事團隊。這也是一種在與怪物戰鬥和改善生活旅程中互相激勵的好方式。",
|
"webFaqAnswer59": "Habitica團隊計劃提供了一種共享體驗,允許隊員輕鬆地在共享任務板上添加、分配和完成任務。憑借成員角色、狀態視圖和任務分配等功能,團隊計劃非常適合擁有共同目標的家庭或同事團隊。這也是一種在與怪物戰鬥和改善生活旅程中互相激勵的好方式。",
|
||||||
"webFaqAnswer60": "這裡有一些小提示來幫助您開啟Habitica團隊計劃的使用: \n\n * 可以提拔成員為管理員,讓他們能夠創建和編輯任務 \n * 如果是任何人都能完成且只需完成1次的任務,請讓該任務保持未分配狀態 \n * 可以將任務分配給某人可以確保其他人無法完成該任務 \n * 如果某個任務需要多人完成,可以將該任務分給多人 \n * 為防止遺漏完成多人共享任務,你的個人任務版面也可以切換顯示共享任務 \n * 即使是分配給多人的任務,你也能因為完成任務而獲得獎勵 \n * 完成任務的獎勵不會在隊員之間拆分 \n * 可以在團隊任務板上使用任務顏色來判斷任務的平均完成率 \n * 記得定期檢查共享任務板上的任務,確保共享任務仍然有效 \n * 錯過完成完成每日任务不会对你或你的团队造成伤害,但该任务的颜色会逐渐变差",
|
"webFaqAnswer60": "這裡有一些小提示來幫助您開啟Habitica團隊計劃的使用: \n\n * 可以提拔成員為管理員,讓他們能夠創建和編輯任務 \n * 如果是任何人都能完成且只需完成1次的任務,請讓該任務保持未分配狀態 \n * 可以將任務分配給某人可以確保其他人無法完成該任務 \n * 如果某個任務需要多人完成,可以將該任務分給多人 \n * 為防止遺漏完成多人共享任務,你的個人任務版面也可以切換顯示共享任務 \n * 即使是分配給多人的任務,你也能因為完成任務而獲得獎勵 \n * 完成任務的獎勵不會在隊員之間拆分 \n * 可以在團隊任務板上使用任務顏色來判斷任務的平均完成率 \n * 記得定期檢查共享任務板上的任務,確保共享任務仍然有效 \n * 錯過完成完成每日任务不会对你或你的团队造成伤害,但该任务的颜色会逐渐变差",
|
||||||
"webFaqAnswer61": "只有團隊隊長和管理員可以創建共享任務。如果你想要某位成員也能創建任務,需要將其提升為管理員。\n\n在網頁端將團隊計劃的成員提升為管理員需要:\n 1. 導航到團隊計劃頁面並切換到“團隊信息”菜單\n 2. 查看成員列表並點擊要提升成員旁的原型按鈕\n 3. 選擇“指定為管理員”",
|
"webFaqAnswer61": "只有團隊隊長和管理員可以創建共享任務。如果你想要某位成員也能創建任務,需要將其提升為管理員。\n\n在網頁端將團隊計劃的成員提升為管理員需要:\n 1. 導航到團隊計劃頁面並切換到“團隊信息”菜單\n 2. 查看成員列表並點擊要提升成員旁的原型按鈕\n 3. 選擇“指定為管理員”",
|
||||||
"faqQuestion62": "該怎麼指派任務?"
|
"faqQuestion62": "該怎麼指派任務?",
|
||||||
|
"webFaqAnswer62": "群組方案讓你能夠將共享任務分配給群組計劃中的其他成員。當共享任務被分配給某個成員時,其他成員將無法完成該任務。\n\n你也可以將任務分配給多個成員。例如,如果每個人都必須刷牙,建立一個任務並將它分配給每位成員。每位成員都可以完成任務並獲得各自的獎勵。當所有人都完成後,主要任務將顯示為已完成。",
|
||||||
|
"faqQuestion63": "未分配的任務是如何運作的?",
|
||||||
|
"webFaqAnswer63": "未分配的任務可以由任何成員完成。例如:倒垃圾。無論誰去倒垃圾,都可以完成這個未分配的任務,並且它會顯示為所有人都已完成。",
|
||||||
|
"faqQuestion64": "同步的每日重置是如何運作的?",
|
||||||
|
"webFaqAnswer64": "共享任務會在同一時間為所有人重置,以保持共享任務板的同步。這個時間會顯示在共享任務板上,並由群組計劃領導者的「一天開始時間」決定。\n由於共享任務會自動重置,所以當你在隔天早上登入時,將沒有機會完成前一天未完成的共享每日任務。\n\n未完成的共享每日任務不會造成傷害,但它們的顏色會逐漸退化,以幫助視覺化進度。",
|
||||||
|
"faqQuestion65": "群組方案在行動應用程式上有支援嗎?",
|
||||||
|
"webFaqAnswer65": "雖然行動應用程式尚未完全支援所有群組方案的功能,但你仍然可以透過 iOS 和 Android 應用程式完成共享任務!\n\n在 Android 上,檢視任務時可以點擊螢幕頂端的顯示名稱,切換到共享任務板。從那裡你可以查看成員、進入聊天,以及建立、完成或分配任務。\n\n你也可以開啟一個偏好設定,把共享任務複製到你的個人任務板,這樣就能在同一個地方完成所有任務。\n\n在行動應用程式上操作方式:\n打開設定,開啟「複製共享任務」\n\n在 Habitica 網站上操作方式:\n前往你的群組方案,並在共享任務板上開啟「複製任務」切換鍵",
|
||||||
|
"faqQuestion66": "群組方案的共享任務與挑戰任務有什麼不同?",
|
||||||
|
"webFaqAnswer66": "群組方案的共享任務板比挑戰更具動態性,因為它們可以不斷更新與互動。挑戰則適合在有一組固定任務要分送給許多人時使用。\n\n群組方案同時也是付費功能,而挑戰對所有人都是免費的。\n\n在挑戰中,你無法指定特定成員去完成某些任務,而且挑戰也沒有共享的每日重置機制。總體來說,挑戰提供的控制與直接互動會比較少。",
|
||||||
|
"sunsetFaqTitle": "Habitica 酒館與公會服務終止 FAQ",
|
||||||
|
"sunsetFaqPara1": "由於多種因素,包括我們玩家群體與 Habitica 的互動方式改變,以及新的內容規範,我們做出了艱難的決定,將於 <strong> 2023 年 8 月 8 日</strong> 終止酒館與公會服務。",
|
||||||
|
"sunsetFaqPara4": "為了紀念我們共同度過的時光,在邁入這個新時代之際,我們將贈送所有人一隻老兵寵物。至於我們出色的貢獻者們,我們也會送上一套特別的裝備組,以紀念他們在 Habitica 社群中的所有努力。",
|
||||||
|
"sunsetFaqPara5": "如果你想了解更多即將變動的內容,可以閱讀以下的詳細資訊。",
|
||||||
|
"sunsetFaqPara3": "我們做出這項決定,是為了能更好地將資源集中在 Habitica 玩家最依賴的部分,同時不會影響任何人的使用權限。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import moment from 'moment';
|
|||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
import sortBy from 'lodash/sortBy';
|
import sortBy from 'lodash/sortBy';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
|
import { body , validationResult } from 'express-validator';
|
||||||
import {
|
import {
|
||||||
authWithHeaders,
|
authWithHeaders,
|
||||||
} from '../../middlewares/auth';
|
} from '../../middlewares/auth';
|
||||||
@@ -87,8 +88,8 @@ api.loginLocal = {
|
|||||||
errorMessage: res.t('missingPassword'),
|
errorMessage: res.t('missingPassword'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
req.sanitizeBody('username').trim();
|
req.sanitizeBody('username').trim();
|
||||||
req.sanitizeBody('password').trim();
|
req.sanitizeBody('password').trim();
|
||||||
@@ -216,8 +217,8 @@ api.updateUsername = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const newUsername = req.body.username;
|
const newUsername = req.body.username;
|
||||||
|
|
||||||
@@ -307,7 +308,7 @@ api.updatePassword = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
|
|
||||||
if (validationErrors) {
|
if (validationErrors) {
|
||||||
throw validationErrors;
|
throw validationErrors;
|
||||||
@@ -353,8 +354,8 @@ api.resetPassword = {
|
|||||||
notEmpty: { errorMessage: res.t('missingEmail') },
|
notEmpty: { errorMessage: res.t('missingEmail') },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const email = req.body.email.toLowerCase();
|
const email = req.body.email.toLowerCase();
|
||||||
let user = await User.findOne(
|
let user = await User.findOne(
|
||||||
@@ -419,12 +420,12 @@ api.updateEmail = {
|
|||||||
|
|
||||||
if (!user.auth.local.email) throw new BadRequest(res.t('userHasNoLocalRegistration'));
|
if (!user.auth.local.email) throw new BadRequest(res.t('userHasNoLocalRegistration'));
|
||||||
|
|
||||||
req.checkBody('newEmail', res.t('newEmailRequired')).notEmpty().isEmail();
|
await body('newEmail', res.t('newEmailRequired')).notEmpty().isEmail().run(req)
|
||||||
if (user.auth.local.hashed_password) {
|
if (user.auth.local.hashed_password) {
|
||||||
req.checkBody('password', res.t('missingPassword')).notEmpty();
|
await body('password', res.t('missingPassword')).notEmpty().run(req)
|
||||||
}
|
}
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const emailAlreadyInUse = await User.findOne({
|
const emailAlreadyInUse = await User.findOne({
|
||||||
'auth.local.email': req.body.newEmail.toLowerCase(),
|
'auth.local.email': req.body.newEmail.toLowerCase(),
|
||||||
@@ -485,8 +486,8 @@ api.resetPasswordSetNewOne = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { newPassword, confirmPassword } = req.body;
|
const { newPassword, confirmPassword } = req.body;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import merge from 'lodash/merge';
|
|||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
import reduce from 'lodash/reduce';
|
import reduce from 'lodash/reduce';
|
||||||
import times from 'lodash/times';
|
import times from 'lodash/times';
|
||||||
|
import { body, param, query , validationResult } from 'express-validator';
|
||||||
import { authWithHeaders, authWithSession } from '../../middlewares/auth';
|
import { authWithHeaders, authWithSession } from '../../middlewares/auth';
|
||||||
import { model as Challenge } from '../../models/challenge';
|
import { model as Challenge } from '../../models/challenge';
|
||||||
import bannedWords from '../../libs/bannedWords';
|
import bannedWords from '../../libs/bannedWords';
|
||||||
@@ -232,11 +233,11 @@ api.createChallenge = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkBody('group', apiError('groupIdRequired')).notEmpty();
|
await body('group', apiError('groupIdRequired')).notEmpty().run(req)
|
||||||
req.checkBody('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_CHALLENGES });
|
await body('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_CHALLENGES }).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const group = await Group.getGroup({
|
const group = await Group.getGroup({
|
||||||
user, groupId: req.body.group, fields: basicGroupFields, optionalMembership: true,
|
user, groupId: req.body.group, fields: basicGroupFields, optionalMembership: true,
|
||||||
@@ -328,10 +329,10 @@ api.joinChallenge = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||||
@@ -397,10 +398,10 @@ api.leaveChallenge = {
|
|||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const keep = req.body.keep === 'remove-all' ? 'remove-all' : 'keep-all';
|
const keep = req.body.keep === 'remove-all' ? 'remove-all' : 'keep-all';
|
||||||
|
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||||
@@ -463,10 +464,10 @@ api.getUserChallenges = {
|
|||||||
url: '/challenges/user',
|
url: '/challenges/user',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkQuery('page').notEmpty().isInt({ min: 0 }, apiError('queryPageInteger'));
|
await query('page').notEmpty().isInt({ min: 0 }, apiError('queryPageInteger')).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const CHALLENGES_PER_PAGE = 10;
|
const CHALLENGES_PER_PAGE = 10;
|
||||||
const {
|
const {
|
||||||
@@ -599,10 +600,10 @@ api.getGroupChallenges = {
|
|||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
let { groupId } = req.params;
|
let { groupId } = req.params;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
if (groupId === 'party') groupId = user.party._id;
|
if (groupId === 'party') groupId = user.party._id;
|
||||||
if (groupId === 'habitrpg') groupId = TAVERN_ID;
|
if (groupId === 'habitrpg') groupId = TAVERN_ID;
|
||||||
@@ -661,10 +662,10 @@ api.getChallenge = {
|
|||||||
url: '/challenges/:challengeId',
|
url: '/challenges/:challengeId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { challengeId } = req.params;
|
const { challengeId } = req.params;
|
||||||
@@ -719,10 +720,10 @@ api.exportChallengeCsv = {
|
|||||||
url: '/challenges/:challengeId/export/csv',
|
url: '/challenges/:challengeId/export/csv',
|
||||||
middlewares: [authWithSession],
|
middlewares: [authWithSession],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { challengeId } = req.params;
|
const { challengeId } = req.params;
|
||||||
@@ -836,11 +837,11 @@ api.updateChallenge = {
|
|||||||
url: '/challenges/:challengeId',
|
url: '/challenges/:challengeId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
req.checkBody('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_CHALLENGES });
|
await body('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_CHALLENGES }).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { challengeId } = req.params;
|
const { challengeId } = req.params;
|
||||||
@@ -883,10 +884,10 @@ api.deleteChallenge = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||||
@@ -931,11 +932,11 @@ api.selectChallengeWinner = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
req.checkParams('winnerId', res.t('winnerIdRequired')).notEmpty().isUUID();
|
await param('winnerId', res.t('winnerIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||||
@@ -992,10 +993,10 @@ api.cloneChallenge = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const challengeToClone = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
const challengeToClone = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||||
if (!challengeToClone) throw new NotFound(res.t('challengeNotFound'));
|
if (!challengeToClone) throw new NotFound(res.t('challengeNotFound'));
|
||||||
@@ -1061,10 +1062,10 @@ api.flagChallenge = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||||
@@ -1094,10 +1095,10 @@ api.clearFlagsChallenge = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
if (!user.hasPermission('moderator')) {
|
if (!user.hasPermission('moderator')) {
|
||||||
throw new NotAuthorized(res.t('messageGroupChatAdminClearFlagCount'));
|
throw new NotAuthorized(res.t('messageGroupChatAdminClearFlagCount'));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import couponCode from 'coupon-code';
|
import couponCode from 'coupon-code';
|
||||||
|
import { param, query , validationResult } from 'express-validator';
|
||||||
import csvStringify from '../../libs/csvStringify';
|
import csvStringify from '../../libs/csvStringify';
|
||||||
import {
|
import {
|
||||||
authWithHeaders,
|
authWithHeaders,
|
||||||
@@ -72,11 +73,11 @@ api.generateCoupons = {
|
|||||||
url: '/coupons/generate/:event',
|
url: '/coupons/generate/:event',
|
||||||
middlewares: [authWithHeaders(), ensurePermission('coupons')],
|
middlewares: [authWithHeaders(), ensurePermission('coupons')],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('event', apiError('eventRequired')).notEmpty();
|
await param('event', apiError('eventRequired')).notEmpty().run(req)
|
||||||
req.checkQuery('count', apiError('countRequired')).notEmpty().isNumeric();
|
await query('count', apiError('countRequired')).notEmpty().isNumeric().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const coupons = await Coupon.generate(req.params.event, req.query.count);
|
const coupons = await Coupon.generate(req.params.event, req.query.count);
|
||||||
res.respond(200, coupons);
|
res.respond(200, coupons);
|
||||||
@@ -122,10 +123,10 @@ api.validateCoupon = {
|
|||||||
optional: true,
|
optional: true,
|
||||||
})],
|
})],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('code', res.t('couponCodeRequired')).notEmpty();
|
await param('code', res.t('couponCodeRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
let valid = false;
|
let valid = false;
|
||||||
const code = couponCode.validate(req.params.code);
|
const code = couponCode.validate(req.params.code);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import pick from 'lodash/pick';
|
|||||||
import uniqBy from 'lodash/uniqBy';
|
import uniqBy from 'lodash/uniqBy';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { body, param, query , validationResult } from 'express-validator';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
import {
|
import {
|
||||||
model as Group,
|
model as Group,
|
||||||
@@ -132,10 +133,10 @@ api.createGroup = {
|
|||||||
const group = new Group(Group.sanitize(req.body));
|
const group = new Group(Group.sanitize(req.body));
|
||||||
group.leader = user._id;
|
group.leader = user._id;
|
||||||
|
|
||||||
req.checkBody('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_GUILDS });
|
await body('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_GUILDS }).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
if (group.type === 'guild') {
|
if (group.type === 'guild') {
|
||||||
if (!user.hasPermission('fullAccess')) {
|
if (!user.hasPermission('fullAccess')) {
|
||||||
@@ -206,10 +207,10 @@ api.createGroupPlan = {
|
|||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const group = new Group(Group.sanitize(req.body.groupToCreate));
|
const group = new Group(Group.sanitize(req.body.groupToCreate));
|
||||||
|
|
||||||
req.checkBody('paymentType', res.t('paymentTypeRequired')).notEmpty();
|
await body('paymentType', res.t('paymentTypeRequired')).notEmpty().run(req)
|
||||||
req.checkBody('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_GUILDS });
|
await body('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_GUILDS }).run(req)
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
// @TODO: Change message
|
// @TODO: Change message
|
||||||
if (group.privacy !== 'private') throw new NotAuthorized(res.t('partyMustbePrivate'));
|
if (group.privacy !== 'private') throw new NotAuthorized(res.t('partyMustbePrivate'));
|
||||||
@@ -321,13 +322,13 @@ api.getGroups = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkQuery('type', res.t('groupTypesRequired')).notEmpty();
|
await query('type', res.t('groupTypesRequired')).notEmpty().run(req)
|
||||||
// pagination options, can only be used with public guilds
|
// pagination options, can only be used with public guilds
|
||||||
req.checkQuery('paginate').optional().isIn(['true', 'false'], apiError('guildsPaginateBooleanString'));
|
await query('paginate').optional().isIn(['true', 'false'], apiError('guildsPaginateBooleanString')).run(req)
|
||||||
req.checkQuery('page').optional().isInt({ min: 0 }, apiError('queryPageInteger'));
|
await query('page').optional().isInt({ min: 0 }, apiError('queryPageInteger')).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const types = req.query.type.split(',');
|
const types = req.query.type.split(',');
|
||||||
|
|
||||||
@@ -419,10 +420,10 @@ api.getGroup = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { groupId } = req.params;
|
const { groupId } = req.params;
|
||||||
const group = await Group.getGroup({ user, groupId, populateLeader: false });
|
const group = await Group.getGroup({ user, groupId, populateLeader: false });
|
||||||
@@ -481,11 +482,11 @@ api.updateGroup = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req);
|
||||||
req.checkBody('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_GUILDS });
|
await body('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_GUILDS }).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
const optionalMembership = Boolean(user.hasPermission('moderator'));
|
const optionalMembership = Boolean(user.hasPermission('moderator'));
|
||||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, optionalMembership });
|
const group = await Group.getGroup({ user, groupId: req.params.groupId, optionalMembership });
|
||||||
|
|
||||||
@@ -567,10 +568,10 @@ api.joinGroup = {
|
|||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
let inviter;
|
let inviter;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty(); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
// Works even if the user is not yet a member of the group
|
// Works even if the user is not yet a member of the group
|
||||||
// Do not fetch chat and work even if the user is not yet a member of the group
|
// Do not fetch chat and work even if the user is not yet a member of the group
|
||||||
@@ -760,10 +761,10 @@ api.rejectGroupInvite = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty(); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { groupId } = req.params;
|
const { groupId } = req.params;
|
||||||
let isUserInvited = false;
|
let isUserInvited = false;
|
||||||
@@ -831,13 +832,13 @@ api.leaveGroup = {
|
|||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req);
|
||||||
// When removing the user from challenges, should we keep the tasks?
|
// When removing the user from challenges, should we keep the tasks?
|
||||||
req.checkQuery('keep', apiError('keepOrRemoveAll')).optional().isIn(['keep-all', 'remove-all']);
|
await query('keep', apiError('keepOrRemoveAll')).optional().isIn(['keep-all', 'remove-all']).run(req)
|
||||||
req.checkBody('keepChallenges', apiError('groupRemainOrLeaveChallenges')).optional().isIn(['remain-in-challenges', 'leave-challenges']);
|
await body('keepChallenges', apiError('groupRemainOrLeaveChallenges')).optional().isIn(['remain-in-challenges', 'leave-challenges']).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { groupId } = req.params;
|
const { groupId } = req.params;
|
||||||
await leaveGroup({
|
await leaveGroup({
|
||||||
@@ -899,11 +900,11 @@ api.removeGroupMember = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req);
|
||||||
req.checkParams('memberId', res.t('userIdRequired')).notEmpty().isUUID();
|
await param('memberId', res.t('userIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
const optionalMembership = Boolean(user.hasPermission('moderator'));
|
const optionalMembership = Boolean(user.hasPermission('moderator'));
|
||||||
const group = await Group.getGroup({
|
const group = await Group.getGroup({
|
||||||
user, groupId: req.params.groupId, optionalMembership, fields: '-chat',
|
user, groupId: req.params.groupId, optionalMembership, fields: '-chat',
|
||||||
@@ -1105,12 +1106,12 @@ api.inviteToGroup = {
|
|||||||
|
|
||||||
if (user.flags.chatRevoked) throw new NotAuthorized(res.t('chatPrivilegesRevoked'));
|
if (user.flags.chatRevoked) throw new NotAuthorized(res.t('chatPrivilegesRevoked'));
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req);
|
||||||
|
|
||||||
if (user.invitesSent >= MAX_EMAIL_INVITES_BY_USER) throw new NotAuthorized(res.t('inviteLimitReached', { techAssistanceEmail: TECH_ASSISTANCE_EMAIL }));
|
if (user.invitesSent >= MAX_EMAIL_INVITES_BY_USER) throw new NotAuthorized(res.t('inviteLimitReached', { techAssistanceEmail: TECH_ASSISTANCE_EMAIL }));
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: '-chat' });
|
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: '-chat' });
|
||||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||||
@@ -1182,11 +1183,11 @@ api.addGroupManager = {
|
|||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { managerId } = req.body;
|
const { managerId } = req.body;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty(); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
||||||
req.checkBody('managerId', apiError('managerIdRequired')).notEmpty();
|
await body('managerId', apiError('managerIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const newManager = await User.findById(managerId, 'guilds party').exec();
|
const newManager = await User.findById(managerId, 'guilds party').exec();
|
||||||
const groupFields = basicGroupFields.concat(' managers');
|
const groupFields = basicGroupFields.concat(' managers');
|
||||||
@@ -1232,11 +1233,11 @@ api.removeGroupManager = {
|
|||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { managerId } = req.body;
|
const { managerId } = req.body;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty(); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
||||||
req.checkBody('managerId', apiError('managerIdRequired')).notEmpty();
|
await body('managerId', apiError('managerIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const groupFields = basicGroupFields.concat(' managers');
|
const groupFields = basicGroupFields.concat(' managers');
|
||||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: groupFields });
|
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: groupFields });
|
||||||
@@ -1323,7 +1324,7 @@ api.getLookingForParty = {
|
|||||||
const USERS_PER_PAGE = 30;
|
const USERS_PER_PAGE = 30;
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkQuery('page').optional().isInt({ min: 0 }, apiError('queryPageInteger'));
|
await query('page').optional().isInt({ min: 0 }, apiError('queryPageInteger')).run(req)
|
||||||
const PAGE = req.query.page || 0;
|
const PAGE = req.query.page || 0;
|
||||||
const PAGE_START = USERS_PER_PAGE * PAGE;
|
const PAGE_START = USERS_PER_PAGE * PAGE;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
|
import { query, param , validationResult } from 'express-validator';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
import { ensurePermission } from '../../middlewares/ensureAccessRight';
|
import { ensurePermission } from '../../middlewares/ensureAccessRight';
|
||||||
import { model as User } from '../../models/user';
|
import { model as User } from '../../models/user';
|
||||||
@@ -73,10 +74,10 @@ api.getPatrons = {
|
|||||||
url: '/hall/patrons',
|
url: '/hall/patrons',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkQuery('page').optional().isInt({ min: 0 }, apiError('queryPageInteger'));
|
await query('page').optional().isInt({ min: 0 }, apiError('queryPageInteger')).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const page = req.query.page ? Number(req.query.page) : 0;
|
const page = req.query.page ? Number(req.query.page) : 0;
|
||||||
const perPage = 50;
|
const perPage = 50;
|
||||||
@@ -177,10 +178,10 @@ api.getHero = {
|
|||||||
url: '/hall/heroes/:heroId',
|
url: '/hall/heroes/:heroId',
|
||||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('heroId', res.t('heroIdRequired')).notEmpty();
|
await param('heroId', res.t('heroIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { heroId } = req.params;
|
const { heroId } = req.params;
|
||||||
|
|
||||||
@@ -263,10 +264,10 @@ api.updateHero = {
|
|||||||
const { heroId } = req.params;
|
const { heroId } = req.params;
|
||||||
const updateData = req.body;
|
const updateData = req.body;
|
||||||
|
|
||||||
req.checkParams('heroId', res.t('heroIdRequired')).notEmpty().isUUID();
|
await param('heroId', res.t('heroIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const hero = await User.findById(heroId).exec();
|
const hero = await User.findById(heroId).exec();
|
||||||
if (!hero) throw new NotFound(res.t('userWithIDNotFound', { userId: heroId }));
|
if (!hero) throw new NotFound(res.t('userWithIDNotFound', { userId: heroId }));
|
||||||
@@ -555,10 +556,10 @@ api.getHeroParty = { // @TODO XXX add tests
|
|||||||
url: '/hall/heroes/party/:groupId',
|
url: '/hall/heroes/party/:groupId',
|
||||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty().isUUID();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { groupId } = req.params;
|
const { groupId } = req.params;
|
||||||
|
|
||||||
@@ -597,10 +598,10 @@ api.getHeroGroupPlans = {
|
|||||||
url: '/hall/heroes/:heroId/group-plans',
|
url: '/hall/heroes/:heroId/group-plans',
|
||||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('heroId', res.t('heroIdRequired')).notEmpty();
|
await param('heroId', res.t('heroIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { heroId } = req.params;
|
const { heroId } = req.params;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import escapeRegExp from 'lodash/escapeRegExp';
|
import escapeRegExp from 'lodash/escapeRegExp';
|
||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
|
import { body, param, query as checkQuery , validationResult } from 'express-validator';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
import {
|
import {
|
||||||
model as User,
|
model as User,
|
||||||
@@ -103,10 +104,10 @@ api.getMember = {
|
|||||||
url: '/members/:memberId',
|
url: '/members/:memberId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
await param('memberId', res.t('memberIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { memberId } = req.params;
|
const { memberId } = req.params;
|
||||||
|
|
||||||
@@ -132,10 +133,10 @@ api.getMemberByUsername = {
|
|||||||
url: '/members/username/:username',
|
url: '/members/username/:username',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('username', res.t('invalidReqParams')).notEmpty();
|
await param('username', res.t('invalidReqParams')).notEmpty().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
let username = req.params.username.toLowerCase();
|
let username = req.params.username.toLowerCase();
|
||||||
if (username[0] === '@') username = username.slice(1, username.length);
|
if (username[0] === '@') username = username.slice(1, username.length);
|
||||||
@@ -261,10 +262,10 @@ api.getMemberAchievements = {
|
|||||||
url: '/members/:memberId/achievements',
|
url: '/members/:memberId/achievements',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
await param('memberId', res.t('memberIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { memberId } = req.params;
|
const { memberId } = req.params;
|
||||||
|
|
||||||
@@ -292,13 +293,13 @@ function _getMembersForItem (type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return async function handleGetMembersForItem (req, res) {
|
return async function handleGetMembersForItem (req, res) {
|
||||||
req.checkParams('groupId', res.t('groupIdRequired')).notEmpty();
|
await param('groupId', res.t('groupIdRequired')).notEmpty().run(req);
|
||||||
req.checkQuery('lastId').optional().notEmpty().isUUID();
|
await checkQuery('lastId').optional().notEmpty().isUUID().run(req)
|
||||||
// Allow an arbitrary number of results (up to 60)
|
// Allow an arbitrary number of results (up to 60)
|
||||||
req.checkQuery('limit', res.t('groupIdRequired')).optional().notEmpty().isInt({ min: 1, max: 60 });
|
await checkQuery('limit', res.t('groupIdRequired')).optional().notEmpty().isInt({ min: 1, max: 60 }).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { groupId } = req.params;
|
const { groupId } = req.params;
|
||||||
const { lastId } = req.query;
|
const { lastId } = req.query;
|
||||||
@@ -566,11 +567,11 @@ api.getChallengeMemberProgress = {
|
|||||||
url: '/challenges/:challengeId/members/:memberId',
|
url: '/challenges/:challengeId/members/:memberId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
await param('memberId', res.t('memberIdRequired')).notEmpty().isUUID().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { challengeId } = req.params;
|
const { challengeId } = req.params;
|
||||||
@@ -627,11 +628,11 @@ api.getObjectionsToInteraction = {
|
|||||||
url: '/members/:toUserId/objections/:interaction',
|
url: '/members/:toUserId/objections/:interaction',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID();
|
await param('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID().run(req);
|
||||||
req.checkParams('interaction', res.t('interactionRequired')).notEmpty().isIn(KNOWN_INTERACTIONS);
|
await param('interaction', res.t('interactionRequired')).notEmpty().isIn(KNOWN_INTERACTIONS).run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const sender = res.locals.user;
|
const sender = res.locals.user;
|
||||||
const receiver = await User.findById(req.params.toUserId).exec();
|
const receiver = await User.findById(req.params.toUserId).exec();
|
||||||
@@ -662,11 +663,11 @@ api.transferGems = {
|
|||||||
url: '/members/transfer-gems',
|
url: '/members/transfer-gems',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkBody('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID();
|
await body('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID().run(req)
|
||||||
req.checkBody('gemAmount', res.t('gemAmountRequired')).notEmpty().isInt();
|
await body('gemAmount', res.t('gemAmountRequired')).notEmpty().isInt().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const sender = res.locals.user;
|
const sender = res.locals.user;
|
||||||
const receiver = await User.findById(req.body.toUserId).exec();
|
const receiver = await User.findById(req.body.toUserId).exec();
|
||||||
@@ -815,9 +816,9 @@ api.clearUserFlags = {
|
|||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { memberId } = req.params;
|
const { memberId } = req.params;
|
||||||
|
|
||||||
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
await param('memberId', res.t('memberIdRequired')).notEmpty().isUUID().run(req);
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
if (!user.hasPermission('moderator')) {
|
if (!user.hasPermission('moderator')) {
|
||||||
throw new BadRequest('Only a moderator may clear reports from a profile.');
|
throw new BadRequest('Only a moderator may clear reports from a profile.');
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { param , validationResult } from 'express-validator';
|
||||||
|
|
||||||
const api = {};
|
const api = {};
|
||||||
|
|
||||||
@@ -36,10 +37,10 @@ api.getModelPaths = {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/models/:model/paths',
|
url: '/models/:model/paths',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('model', res.t('modelNotFound')).notEmpty().isIn(allModels);
|
await param('model', res.t('modelNotFound')).notEmpty().isIn(allModels).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
let { model } = req.params;
|
let { model } = req.params;
|
||||||
// tasks models are lowercase, the others have the first letter uppercase (User, Group)
|
// tasks models are lowercase, the others have the first letter uppercase (User, Group)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { body, param , validationResult } from 'express-validator';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
import {
|
import {
|
||||||
NotFound,
|
NotFound,
|
||||||
@@ -25,11 +26,11 @@ api.addPushDevice = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkBody('regId', res.t('regIdRequired')).notEmpty();
|
await body('regId', res.t('regIdRequired')).notEmpty().run(req)
|
||||||
req.checkBody('type', res.t('typeRequired')).notEmpty().isIn(['ios', 'android']);
|
await body('type', res.t('typeRequired')).notEmpty().isIn(['ios', 'android']).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { pushDevices } = user;
|
const { pushDevices } = user;
|
||||||
|
|
||||||
@@ -75,10 +76,10 @@ api.removePushDevice = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('regId', res.t('regIdRequired')).notEmpty();
|
await param('regId', res.t('regIdRequired')).notEmpty().run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { regId } = req.params;
|
const { regId } = req.params;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import each from 'lodash/each';
|
|||||||
import every from 'lodash/every';
|
import every from 'lodash/every';
|
||||||
import isBoolean from 'lodash/isBoolean';
|
import isBoolean from 'lodash/isBoolean';
|
||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
|
import { param , validationResult } from 'express-validator';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
import { getAnalyticsServiceByEnvironment } from '../../libs/analyticsService';
|
import { getAnalyticsServiceByEnvironment } from '../../libs/analyticsService';
|
||||||
import {
|
import {
|
||||||
@@ -68,10 +69,10 @@ api.inviteToQuest = {
|
|||||||
const { questKey } = req.params;
|
const { questKey } = req.params;
|
||||||
const quest = questScrolls[questKey];
|
const quest = questScrolls[questKey];
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: basicGroupFields.concat(' quest chat') });
|
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: basicGroupFields.concat(' quest chat') });
|
||||||
|
|
||||||
@@ -202,10 +203,10 @@ api.acceptQuest = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: basicGroupFields.concat(' quest chat') });
|
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: basicGroupFields.concat(' quest chat') });
|
||||||
|
|
||||||
@@ -267,10 +268,10 @@ api.rejectQuest = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: basicGroupFields.concat(' quest chat') });
|
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: basicGroupFields.concat(' quest chat') });
|
||||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||||
@@ -335,10 +336,10 @@ api.forceStart = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: basicGroupFields.concat(' quest chat') });
|
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: basicGroupFields.concat(' quest chat') });
|
||||||
|
|
||||||
@@ -400,10 +401,10 @@ api.cancelQuest = {
|
|||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { groupId } = req.params;
|
const { groupId } = req.params;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const group = await Group.getGroup({ user, groupId, fields: basicGroupFields.concat(' quest') });
|
const group = await Group.getGroup({ user, groupId, fields: basicGroupFields.concat(' quest') });
|
||||||
|
|
||||||
@@ -475,10 +476,10 @@ api.abortQuest = {
|
|||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { groupId } = req.params;
|
const { groupId } = req.params;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const group = await Group.getGroup({ user, groupId, fields: basicGroupFields.concat(' quest chat') });
|
const group = await Group.getGroup({ user, groupId, fields: basicGroupFields.concat(' quest chat') });
|
||||||
|
|
||||||
@@ -549,10 +550,10 @@ api.leaveQuest = {
|
|||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { groupId } = req.params;
|
const { groupId } = req.params;
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const group = await Group.getGroup({ user, groupId, fields: basicGroupFields.concat(' quest') });
|
const group = await Group.getGroup({ user, groupId, fields: basicGroupFields.concat(' quest') });
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import assign from 'lodash/assign';
|
|||||||
import find from 'lodash/find';
|
import find from 'lodash/find';
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
|
import { param, query , validationResult } from 'express-validator'
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
import {
|
import {
|
||||||
@@ -310,9 +311,9 @@ api.createChallengeTasks = {
|
|||||||
url: '/tasks/challenge/:challengeId',
|
url: '/tasks/challenge/:challengeId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const reqValidationErrors = req.validationErrors();
|
const reqValidationErrors = validationResult(req).array();
|
||||||
if (reqValidationErrors) throw reqValidationErrors;
|
if (reqValidationErrors) throw reqValidationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
@@ -394,10 +395,10 @@ api.getUserTasks = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const types = Tasks.tasksTypes.map(type => `${type}s`);
|
const types = Tasks.tasksTypes.map(type => `${type}s`);
|
||||||
types.push('completedTodos', '_allCompletedTodos'); // _allCompletedTodos is currently in BETA and is likely to be removed in future
|
types.push('completedTodos', '_allCompletedTodos'); // _allCompletedTodos is currently in BETA and is likely to be removed in future
|
||||||
req.checkQuery('type', res.t('invalidTasksTypeExtra')).optional().isIn(types);
|
await query('type', res.t('invalidTasksTypeExtra')).optional().isIn(types).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { dueDate } = req.query;
|
const { dueDate } = req.query;
|
||||||
@@ -447,12 +448,12 @@ api.getChallengeTasks = {
|
|||||||
url: '/tasks/challenge/:challengeId',
|
url: '/tasks/challenge/:challengeId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req)
|
||||||
const types = Tasks.tasksTypes.map(type => `${type}s`);
|
const types = Tasks.tasksTypes.map(type => `${type}s`);
|
||||||
req.checkQuery('type', res.t('invalidTasksType')).optional().isIn(types);
|
await query('type', res.t('invalidTasksType')).optional().isIn(types).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { challengeId } = req.params;
|
const { challengeId } = req.params;
|
||||||
@@ -602,10 +603,10 @@ api.updateTask = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { taskId } = req.params;
|
const { taskId } = req.params;
|
||||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||||
@@ -805,11 +806,11 @@ api.moveTask = {
|
|||||||
url: '/tasks/:taskId/move/to/:position',
|
url: '/tasks/:taskId/move/to/:position',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||||
req.checkParams('position', res.t('positionRequired')).notEmpty().isNumeric();
|
await param('position', res.t('positionRequired')).notEmpty().isNumeric().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { taskId } = req.params;
|
const { taskId } = req.params;
|
||||||
@@ -908,10 +909,10 @@ api.addChecklistItem = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { taskId } = req.params;
|
const { taskId } = req.params;
|
||||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||||
@@ -957,11 +958,11 @@ api.scoreCheckListItem = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
await param('itemId', res.t('itemIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { taskId } = req.params;
|
const { taskId } = req.params;
|
||||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||||
@@ -1018,11 +1019,11 @@ api.updateChecklistItem = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
await param('itemId', res.t('itemIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { taskId } = req.params;
|
const { taskId } = req.params;
|
||||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||||
@@ -1079,11 +1080,11 @@ api.removeChecklistItem = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
await param('itemId', res.t('itemIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { taskId } = req.params;
|
const { taskId } = req.params;
|
||||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||||
@@ -1141,12 +1142,12 @@ api.addTagToTask = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||||
const userTags = user.tags.map(tag => tag.id);
|
const userTags = user.tags.map(tag => tag.id);
|
||||||
req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID().isIn(userTags);
|
await param('tagId', res.t('tagIdRequired')).notEmpty().isUUID().isIn(userTags).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { taskId } = req.params;
|
const { taskId } = req.params;
|
||||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id, { userId: user._id });
|
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id, { userId: user._id });
|
||||||
@@ -1198,11 +1199,11 @@ api.removeTagFromTask = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||||
req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID();
|
await param('tagId', res.t('tagIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { taskId } = req.params;
|
const { taskId } = req.params;
|
||||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id, { userId: user._id });
|
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id, { userId: user._id });
|
||||||
@@ -1243,11 +1244,11 @@ api.unlinkAllTasks = {
|
|||||||
url: '/tasks/unlink-all/:challengeId',
|
url: '/tasks/unlink-all/:challengeId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req)
|
||||||
req.checkQuery('keep', apiError('keepOrRemoveAll')).notEmpty().isIn(['keep-all', 'remove-all']);
|
await query('keep', apiError('keepOrRemoveAll')).notEmpty().isIn(['keep-all', 'remove-all']).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { keep } = req.query;
|
const { keep } = req.query;
|
||||||
@@ -1309,11 +1310,11 @@ api.unlinkOneTask = {
|
|||||||
url: '/tasks/unlink-one/:taskId',
|
url: '/tasks/unlink-one/:taskId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty().isUUID();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().isUUID().run(req)
|
||||||
req.checkQuery('keep', apiError('keepOrRemove')).notEmpty().isIn(['keep', 'remove']);
|
await query('keep', apiError('keepOrRemove')).notEmpty().isIn(['keep', 'remove']).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { keep } = req.query;
|
const { keep } = req.query;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
import isUUID from 'validator/lib/isUUID';
|
import isUUID from 'validator/lib/isUUID';
|
||||||
|
import { param , query , validationResult } from 'express-validator';
|
||||||
import { authWithHeaders } from '../../../middlewares/auth';
|
import { authWithHeaders } from '../../../middlewares/auth';
|
||||||
import * as Tasks from '../../../models/task';
|
import * as Tasks from '../../../models/task';
|
||||||
import { model as Group } from '../../../models/group';
|
import { model as Group } from '../../../models/group';
|
||||||
@@ -45,9 +46,9 @@ api.createGroupTasks = {
|
|||||||
url: '/tasks/group/:groupId',
|
url: '/tasks/group/:groupId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty().isUUID();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const reqValidationErrors = req.validationErrors();
|
const reqValidationErrors = validationResult(req).array();
|
||||||
if (reqValidationErrors) throw reqValidationErrors;
|
if (reqValidationErrors) throw reqValidationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
@@ -92,11 +93,11 @@ api.getGroupTasks = {
|
|||||||
url: '/tasks/group/:groupId',
|
url: '/tasks/group/:groupId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty().isUUID();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().isUUID().run(req)
|
||||||
req.checkQuery('type', res.t('invalidTasksType')).optional().isIn(types);
|
await query('type', res.t('invalidTasksType')).optional().isIn(types).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
@@ -132,10 +133,10 @@ api.groupMoveTask = {
|
|||||||
url: '/group-tasks/:taskId/move/to/:position',
|
url: '/group-tasks/:taskId/move/to/:position',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||||
req.checkParams('position', res.t('positionRequired')).notEmpty().isNumeric();
|
await param('position', res.t('positionRequired')).notEmpty().isNumeric().run(req)
|
||||||
|
|
||||||
const reqValidationErrors = req.validationErrors();
|
const reqValidationErrors = validationResult(req).array();
|
||||||
if (reqValidationErrors) throw reqValidationErrors;
|
if (reqValidationErrors) throw reqValidationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
@@ -203,9 +204,9 @@ api.assignTask = {
|
|||||||
url: '/tasks/:taskId/assign',
|
url: '/tasks/:taskId/assign',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty().isUUID();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const reqValidationErrors = req.validationErrors();
|
const reqValidationErrors = validationResult(req).array();
|
||||||
if (reqValidationErrors) throw reqValidationErrors;
|
if (reqValidationErrors) throw reqValidationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
@@ -280,10 +281,10 @@ api.unassignTask = {
|
|||||||
url: '/tasks/:taskId/unassign/:assignedUserId',
|
url: '/tasks/:taskId/unassign/:assignedUserId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty().isUUID();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().isUUID().run(req)
|
||||||
req.checkParams('assignedUserId', res.t('userIdRequired')).notEmpty().isUUID();
|
await param('assignedUserId', res.t('userIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const reqValidationErrors = req.validationErrors();
|
const reqValidationErrors = validationResult(req).array();
|
||||||
if (reqValidationErrors) throw reqValidationErrors;
|
if (reqValidationErrors) throw reqValidationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
@@ -337,10 +338,10 @@ api.taskNeedsWork = {
|
|||||||
url: '/tasks/:taskId/needs-work/:userId',
|
url: '/tasks/:taskId/needs-work/:userId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty().isUUID();
|
await param('taskId', apiError('taskIdRequired')).notEmpty().isUUID().run(req)
|
||||||
req.checkParams('userId', res.t('userIdRequired')).notEmpty().isUUID();
|
await param('userId', res.t('userIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const reqValidationErrors = req.validationErrors();
|
const reqValidationErrors = validationResult(req).array();
|
||||||
if (reqValidationErrors) throw reqValidationErrors;
|
if (reqValidationErrors) throw reqValidationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import isFunction from 'lodash/isFunction';
|
|||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
import { param , validationResult } from 'express-validator';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
import common from '../../../common';
|
import common from '../../../common';
|
||||||
import {
|
import {
|
||||||
@@ -1735,11 +1736,11 @@ api.movePinnedItem = {
|
|||||||
url: '/user/move-pinned-item/:path/move/to/:position',
|
url: '/user/move-pinned-item/:path/move/to/:position',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('path', res.t('taskIdRequired')).notEmpty();
|
await param('path', res.t('taskIdRequired')).notEmpty().run(req)
|
||||||
req.checkParams('position', res.t('positionRequired')).notEmpty().isNumeric();
|
await param('position', res.t('positionRequired')).notEmpty().isNumeric().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { path } = req.params;
|
const { path } = req.params;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
|
import { param , validationResult } from 'express-validator';
|
||||||
import uniqBy from 'lodash/uniqBy';
|
import uniqBy from 'lodash/uniqBy';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
@@ -41,10 +42,10 @@ api.searchHero = {
|
|||||||
url: '/admin/search/:userIdentifier',
|
url: '/admin/search/:userIdentifier',
|
||||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('userIdentifier', res.t('userIdentifierRequired')).notEmpty();
|
await param('userIdentifier', res.t('userIdentifierRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { userIdentifier } = req.params;
|
const { userIdentifier } = req.params;
|
||||||
|
|
||||||
@@ -109,10 +110,10 @@ api.getUserHistory = {
|
|||||||
url: '/admin/user/:userId/history',
|
url: '/admin/user/:userId/history',
|
||||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('userId', res.t('heroIdRequired')).notEmpty().isUUID();
|
await param('userId', res.t('heroIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { userId } = req.params;
|
const { userId } = req.params;
|
||||||
|
|
||||||
@@ -161,10 +162,10 @@ api.updateBlocker = {
|
|||||||
url: '/admin/blockers/:blockerId',
|
url: '/admin/blockers/:blockerId',
|
||||||
middlewares: [authWithHeaders(), ensurePermission('accessControl')],
|
middlewares: [authWithHeaders(), ensurePermission('accessControl')],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID();
|
await param('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const blocker = await Blocker.findById(req.params.blockerId).exec();
|
const blocker = await Blocker.findById(req.params.blockerId).exec();
|
||||||
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
|
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
|
||||||
@@ -181,10 +182,10 @@ api.deleteBlocker = {
|
|||||||
url: '/admin/blockers/:blockerId',
|
url: '/admin/blockers/:blockerId',
|
||||||
middlewares: [authWithHeaders(), ensurePermission('accessControl')],
|
middlewares: [authWithHeaders(), ensurePermission('accessControl')],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID();
|
await param('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const blocker = await Blocker.findById(req.params.blockerId).exec();
|
const blocker = await Blocker.findById(req.params.blockerId).exec();
|
||||||
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
|
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ api.verifyUsername = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const chosenUsername = req.body.username;
|
const chosenUsername = req.body.username;
|
||||||
@@ -105,8 +105,8 @@ api.checkEmail = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const lowercaseEmail = req.body.email.toLowerCase();
|
const lowercaseEmail = req.body.email.toLowerCase();
|
||||||
if (isRestrictedEmailDomain(lowercaseEmail)) {
|
if (isRestrictedEmailDomain(lowercaseEmail)) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { query , validationResult } from 'express-validator';
|
||||||
import { langCodes } from '../../libs/i18n';
|
import { langCodes } from '../../libs/i18n';
|
||||||
import { apiError } from '../../libs/apiError';
|
import { apiError } from '../../libs/apiError';
|
||||||
import common from '../../../common';
|
import common from '../../../common';
|
||||||
@@ -57,10 +58,10 @@ api.faq = {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/faq',
|
url: '/faq',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkQuery('platform').optional().isIn(['web', 'android', 'ios'], apiError('invalidPlatform'));
|
await query('platform').optional().isIn(['web', 'android', 'ios'], apiError('invalidPlatform')).run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const proposedLang = req.query.language && req.query.language.toString();
|
const proposedLang = req.query.language && req.query.language.toString();
|
||||||
const language = langCodes.includes(proposedLang) ? proposedLang : 'en';
|
const language = langCodes.includes(proposedLang) ? proposedLang : 'en';
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { param , validationResult } from 'express-validator';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
import { apiError } from '../../libs/apiError';
|
import { apiError } from '../../libs/apiError';
|
||||||
import { NotFound } from '../../libs/errors';
|
import { NotFound } from '../../libs/errors';
|
||||||
@@ -38,10 +39,10 @@ api.deleteMessage = {
|
|||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
url: '/inbox/messages/:messageId',
|
url: '/inbox/messages/:messageId',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('messageId', apiError('messageIdRequired')).notEmpty().isUUID();
|
await param('messageId', apiError('messageIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { messageId } = req.params;
|
const { messageId } = req.params;
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
@@ -216,10 +217,10 @@ api.likePrivateMessage = {
|
|||||||
url: '/inbox/like-private-message/:uniqueMessageId',
|
url: '/inbox/like-private-message/:uniqueMessageId',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('uniqueMessageId', apiError('messageIdRequired')).notEmpty();
|
await param('uniqueMessageId', apiError('messageIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
const { uniqueMessageId } = req.params;
|
const { uniqueMessageId } = req.params;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { sendJob } from '../../libs/worker';
|
import { sendJob } from '../../libs/worker';
|
||||||
|
import { param , validationResult } from 'express-validator';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
import { ensurePermission } from '../../middlewares/ensureAccessRight';
|
import { ensurePermission } from '../../middlewares/ensureAccessRight';
|
||||||
import { TransactionModel as Transaction } from '../../models/transaction';
|
import { TransactionModel as Transaction } from '../../models/transaction';
|
||||||
@@ -16,9 +17,9 @@ api.purchaseHistory = {
|
|||||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||||
url: '/members/:memberId/purchase-history',
|
url: '/members/:memberId/purchase-history',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
await param('memberId', res.t('memberIdRequired')).notEmpty().isUUID().run(req)
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
let transactions = await Transaction
|
let transactions = await Transaction
|
||||||
.find({ userId: req.params.memberId })
|
.find({ userId: req.params.memberId })
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { param , validationResult } from 'express-validator';
|
||||||
import { authWithHeaders } from '../../middlewares/auth';
|
import { authWithHeaders } from '../../middlewares/auth';
|
||||||
import { apiError } from '../../libs/apiError';
|
import { apiError } from '../../libs/apiError';
|
||||||
import { model as NewsPost } from '../../models/newsPost';
|
import { model as NewsPost } from '../../models/newsPost';
|
||||||
@@ -110,7 +111,7 @@ api.getPost = {
|
|||||||
})],
|
})],
|
||||||
noLanguage: true,
|
noLanguage: true,
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('postId', apiError('postIdRequired')).notEmpty().isUUID();
|
await param('postId', apiError('postIdRequired')).notEmpty().isUUID().run(req)
|
||||||
const { user } = res.locals;
|
const { user } = res.locals;
|
||||||
|
|
||||||
const newsPost = await NewsPost.findById(req.params.postId).exec();
|
const newsPost = await NewsPost.findById(req.params.postId).exec();
|
||||||
@@ -148,9 +149,9 @@ api.updateNews = {
|
|||||||
url: '/news/:postId',
|
url: '/news/:postId',
|
||||||
middlewares: [authWithHeaders(), ensurePermission('news')],
|
middlewares: [authWithHeaders(), ensurePermission('news')],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('postId', apiError('postIdRequired')).notEmpty().isUUID();
|
await param('postId', apiError('postIdRequired')).notEmpty().isUUID().run(req)
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const newsPost = await NewsPost.findById(req.params.postId).exec();
|
const newsPost = await NewsPost.findById(req.params.postId).exec();
|
||||||
if (!newsPost) throw new NotFound(res.t('newsPostNotFound'));
|
if (!newsPost) throw new NotFound(res.t('newsPostNotFound'));
|
||||||
@@ -183,9 +184,9 @@ api.deleteNews = {
|
|||||||
url: '/news/:postId',
|
url: '/news/:postId',
|
||||||
middlewares: [authWithHeaders(), ensurePermission('news')],
|
middlewares: [authWithHeaders(), ensurePermission('news')],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
req.checkParams('postId', apiError('postIdRequired')).notEmpty().isUUID();
|
await param('postId', apiError('postIdRequired')).notEmpty().isUUID().run(req)
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const newsPost = await NewsPost.findById(req.params.postId).exec();
|
const newsPost = await NewsPost.findById(req.params.postId).exec();
|
||||||
if (!newsPost) throw new NotFound(res.t('newsPostNotFound'));
|
if (!newsPost) throw new NotFound(res.t('newsPostNotFound'));
|
||||||
|
|||||||
@@ -252,8 +252,8 @@ api.verifyDisplayName = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const chosenDisplayName = req.body.displayName;
|
const chosenDisplayName = req.body.displayName;
|
||||||
|
|
||||||
|
|||||||
@@ -177,10 +177,10 @@ api.exportUserAvatarHtml = {
|
|||||||
async handler (/* req, res */) {
|
async handler (/* req, res */) {
|
||||||
throw new NotFound('This API route is currently not available. See https://github.com/HabitRPG/habitica/issues/9489.');
|
throw new NotFound('This API route is currently not available. See https://github.com/HabitRPG/habitica/issues/9489.');
|
||||||
|
|
||||||
/* req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
/* await param('memberId', res.t('memberIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { memberId } = req.params;
|
const { memberId } = req.params;
|
||||||
|
|
||||||
@@ -215,10 +215,10 @@ api.exportUserAvatarPng = {
|
|||||||
async handler (/* req, res */) {
|
async handler (/* req, res */) {
|
||||||
throw new NotFound('This API route is currently not available. See https://github.com/HabitRPG/habitica/issues/9489.');
|
throw new NotFound('This API route is currently not available. See https://github.com/HabitRPG/habitica/issues/9489.');
|
||||||
|
|
||||||
/* req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
/* await param('memberId', res.t('memberIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { memberId } = req.params;
|
const { memberId } = req.params;
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ async function emailUnsubscribe (req, res) {
|
|||||||
notEmpty: { errorMessage: res.t('missingUnsubscriptionCode') },
|
notEmpty: { errorMessage: res.t('missingUnsubscriptionCode') },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const data = JSON.parse(decrypt(req.query.code));
|
const data = JSON.parse(decrypt(req.query.code));
|
||||||
|
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ async function registerLocal (req, res, { isV3 = false }) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const issues = verifyUsername(req.body.username, res);
|
const issues = verifyUsername(req.body.username, res);
|
||||||
if (issues.length > 0) throw new BadRequest(issues.join(' '));
|
if (issues.length > 0) throw new BadRequest(issues.join(' '));
|
||||||
|
|||||||
@@ -17,11 +17,7 @@ export function loginRes (user, req, res) {
|
|||||||
if (user.auth.blocked) {
|
if (user.auth.blocked) {
|
||||||
throw new NotAuthorized(res.t(
|
throw new NotAuthorized(res.t(
|
||||||
'accountSuspended',
|
'accountSuspended',
|
||||||
{
|
{ communityManagerEmail: COMMUNITY_MANAGER_EMAIL, userId: user._id },
|
||||||
communityManagerEmail: COMMUNITY_MANAGER_EMAIL,
|
|
||||||
userId: user._id,
|
|
||||||
username: user.auth.local.username,
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
const urlPath = url.parse(req.url).pathname;
|
const urlPath = url.parse(req.url).pathname;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { param, query , validationResult } from 'express-validator';
|
||||||
import {
|
import {
|
||||||
model as User,
|
model as User,
|
||||||
publicFields as memberFields,
|
publicFields as memberFields,
|
||||||
@@ -22,13 +23,13 @@ async function getMembersTasksForChallenge (members, challenge) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function handleGetMembersForChallenge (req, res) {
|
export async function handleGetMembersForChallenge (req, res) {
|
||||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||||
req.checkQuery('lastId').optional().notEmpty().isUUID();
|
await query('lastId').optional().notEmpty().isUUID().run(req);
|
||||||
// Allow an arbitrary number of results (up to 60)
|
// Allow an arbitrary number of results (up to 60)
|
||||||
req.checkQuery('limit', res.t('groupIdRequired')).optional().notEmpty().isInt({ min: 1, max: 60 });
|
await query('limit', res.t('groupIdRequired')).optional().notEmpty().isInt({ min: 1, max: 60 }).run(req);
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const { challengeId } = req.params;
|
const { challengeId } = req.params;
|
||||||
const { lastId } = req.query;
|
const { lastId } = req.query;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { param , validationResult } from 'express-validator';
|
||||||
import { getAuthorEmailFromMessage } from '../chat';
|
import { getAuthorEmailFromMessage } from '../chat';
|
||||||
import ChatReporter from './chatReporter';
|
import ChatReporter from './chatReporter';
|
||||||
import {
|
import {
|
||||||
@@ -23,11 +24,11 @@ export default class GroupChatReporter extends ChatReporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async validate () {
|
async validate () {
|
||||||
this.req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||||
this.req.checkParams('chatId', apiError('chatIdRequired')).notEmpty();
|
await param('chatId', apiError('chatIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = this.req.validationErrors();
|
const validationErrors = this.validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const group = await Group.getGroup({
|
const group = await Group.getGroup({
|
||||||
user: this.user,
|
user: this.user,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { param , validationResult } from 'express-validator';
|
||||||
import { model as User } from '../../models/user';
|
import { model as User } from '../../models/user';
|
||||||
|
|
||||||
import ChatReporter from './chatReporter';
|
import ChatReporter from './chatReporter';
|
||||||
@@ -20,10 +21,10 @@ export default class InboxChatReporter extends ChatReporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async validate () {
|
async validate () {
|
||||||
this.req.checkParams('messageId', apiError('messageIdRequired')).notEmpty();
|
await param('messageId', apiError('messageIdRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = this.req.validationErrors();
|
const validationErrors = this.validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
if (this.user.hasPermission('moderator') && this.req.query.userId) {
|
if (this.user.hasPermission('moderator') && this.req.query.userId) {
|
||||||
this.inboxUser = await User.findOne({ _id: this.req.query.userId }).exec();
|
this.inboxUser = await User.findOne({ _id: this.req.query.userId }).exec();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { model as User } from '../../models/user';
|
import { model as User } from '../../models/user';
|
||||||
import * as slack from '../slack';
|
import * as slack from '../slack';
|
||||||
|
import { param , validationResult } from 'express-validator';
|
||||||
|
|
||||||
import ChatReporter from './chatReporter';
|
import ChatReporter from './chatReporter';
|
||||||
import {
|
import {
|
||||||
@@ -15,10 +16,10 @@ export default class ProfileReporter extends ChatReporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async validate () {
|
async validate () {
|
||||||
this.req.checkParams('memberId', this.res.t('memberIdRequired')).notEmpty().isUUID();
|
await param('memberId', this.res.t('memberIdRequired')).notEmpty().isUUID().run(req)
|
||||||
|
|
||||||
const validationErrors = this.req.validationErrors();
|
const validationErrors = this.validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
const flaggedUser = await User.findOne(
|
const flaggedUser = await User.findOne(
|
||||||
{ _id: this.req.params.memberId },
|
{ _id: this.req.params.memberId },
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { param , validationResult } from 'express-validator';
|
||||||
import { model as Coupon } from '../../models/coupon';
|
import { model as Coupon } from '../../models/coupon';
|
||||||
|
|
||||||
export async function enterCode (req, res, user) { // eslint-disable-line import/prefer-default-export, max-len
|
export async function enterCode (req, res, user) { // eslint-disable-line import/prefer-default-export, max-len
|
||||||
req.checkParams('code', res.t('couponCodeRequired')).notEmpty();
|
await param('code', res.t('couponCodeRequired')).notEmpty().run(req)
|
||||||
|
|
||||||
const validationErrors = req.validationErrors();
|
const validationErrors = validationResult(req).array();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
await Coupon.apply(user, req, req.params.code);
|
await Coupon.apply(user, req, req.params.code);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,18 +164,24 @@ export default async function highlightMentions (text) {
|
|||||||
|
|
||||||
if (mentions && mentions.length <= 5) {
|
if (mentions && mentions.length <= 5) {
|
||||||
const usernames = mentions.map(mention => mention.substr(1));
|
const usernames = mentions.map(mention => mention.substr(1));
|
||||||
|
const usernameRegexes = usernames.map(username => new RegExp(`^${escapeRegExp(username)}$`, 'i'));
|
||||||
members = await User
|
members = await User
|
||||||
.find({ 'auth.local.username': { $in: usernames }, 'flags.verifiedUsername': true })
|
.find({
|
||||||
|
$or: usernameRegexes.map(regex => ({ 'auth.local.username': regex })),
|
||||||
|
'flags.verifiedUsername': true,
|
||||||
|
})
|
||||||
.select(['auth.local.username', '_id', 'preferences.pushNotifications', 'pushDevices', 'party', 'guilds'])
|
.select(['auth.local.username', '_id', 'preferences.pushNotifications', 'pushDevices', 'party', 'guilds'])
|
||||||
.lean()
|
.lean()
|
||||||
.exec();
|
.exec();
|
||||||
const baseUrl = determineBaseUrl();
|
const baseUrl = determineBaseUrl();
|
||||||
members.forEach(member => {
|
members.forEach(member => {
|
||||||
const { username } = member.auth.local;
|
const { username } = member.auth.local;
|
||||||
const regex = new RegExp(`@${username}(?![\\-\\w])`, 'g');
|
const regex = new RegExp(`@${escapeRegExp(username)}(?![\\-\\w])`, 'gi');
|
||||||
const replacement = `[@${username}](${baseUrl}/profile/${member._id})`;
|
|
||||||
|
|
||||||
textBlocks.transformValidBlocks(blockText => blockText.replace(regex, replacement));
|
textBlocks.transformValidBlocks(blockText => blockText.replace(regex, match => {
|
||||||
|
const mentionedUsername = match.substr(1);
|
||||||
|
return `[@${mentionedUsername}](${baseUrl}/profile/${member._id})`;
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
|
import { query, validationResult } from 'express-validator';
|
||||||
import { model as User } from '../models/user';
|
import { model as User } from '../models/user';
|
||||||
import { chatModel as Chat } from '../models/message';
|
import { chatModel as Chat } from '../models/message';
|
||||||
import * as Tasks from '../models/task';
|
import * as Tasks from '../models/task';
|
||||||
@@ -157,9 +159,9 @@ async function castSpell (req, res, { isV3 = false }) {
|
|||||||
const quantity = req.body.quantity || 1;
|
const quantity = req.body.quantity || 1;
|
||||||
|
|
||||||
// optional because not required by all targetTypes, presence is checked later if necessary
|
// optional because not required by all targetTypes, presence is checked later if necessary
|
||||||
req.checkQuery('targetId', res.t('targetIdUUID')).optional().isUUID();
|
await query('targetId', res.t('targetIdUUID')).optional().isUUID().run(req)
|
||||||
|
|
||||||
const reqValidationErrors = req.validationErrors();
|
const reqValidationErrors = validationResult(req).array();
|
||||||
if (reqValidationErrors) throw reqValidationErrors;
|
if (reqValidationErrors) throw reqValidationErrors;
|
||||||
|
|
||||||
const klass = common.content.spells.special[spellId] ? 'special' : user.stats.class;
|
const klass = common.content.spells.special[spellId] ? 'special' : user.stats.class;
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ async function createTasks (req, res, options = {}) {
|
|||||||
// and task in parallel it could save the user/challenge/group
|
// and task in parallel it could save the user/challenge/group
|
||||||
// with a tasksOrder that doens't match reality
|
// with a tasksOrder that doens't match reality
|
||||||
const validationErrors = newTask.validateSync();
|
const validationErrors = newTask.validateSync();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||||
|
|
||||||
// Otherwise update the user/challenge/group
|
// Otherwise update the user/challenge/group
|
||||||
if (!taskOrderToAdd[`${taskType}s`]) taskOrderToAdd[`${taskType}s`] = [];
|
if (!taskOrderToAdd[`${taskType}s`]) taskOrderToAdd[`${taskType}s`] = [];
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import expressValidator from 'express-validator';
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import analytics from './analytics';
|
import analytics from './analytics';
|
||||||
import setupBody from './setupBody';
|
import setupBody from './setupBody';
|
||||||
@@ -16,7 +15,6 @@ const app = express();
|
|||||||
// re-set the view options because they are not inherited from the top level app
|
// re-set the view options because they are not inherited from the top level app
|
||||||
setupExpress(app);
|
setupExpress(app);
|
||||||
|
|
||||||
app.use(expressValidator());
|
|
||||||
app.use(analytics);
|
app.use(analytics);
|
||||||
app.use(setupBody);
|
app.use(setupBody);
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ export function authWithHeaders (options = {}) {
|
|||||||
throw new NotAuthorized(common.i18n.t('accountSuspended', {
|
throw new NotAuthorized(common.i18n.t('accountSuspended', {
|
||||||
communityManagerEmail: COMMUNITY_MANAGER_EMAIL,
|
communityManagerEmail: COMMUNITY_MANAGER_EMAIL,
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
username: user.auth.local.username,
|
|
||||||
}, language));
|
}, language));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ export default function errorHandler (err, req, res, next) { // eslint-disable-l
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle errors by express-validator
|
// Handle errors by express-validator
|
||||||
if (Array.isArray(err) && err[0].param && err[0].msg) {
|
if (Array.isArray(err) && err.length >= 1 && err[0].path && err[0].msg) {
|
||||||
responseErr = new BadRequest(res.t('invalidReqParams'));
|
responseErr = new BadRequest(res.t('invalidReqParams'));
|
||||||
responseErr.errors = err.map(paramErr => ({
|
responseErr.errors = err.map(paramErr => ({
|
||||||
message: paramErr.msg,
|
message: paramErr.msg,
|
||||||
param: paramErr.param,
|
param: paramErr.path,
|
||||||
value: paramErr.value,
|
value: paramErr.value,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ export default function errorHandler (err, req, res, next) { // eslint-disable-l
|
|||||||
responseErr = new InternalServerError();
|
responseErr = new InternalServerError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!err.skipLogging) {
|
if (!err.skipLogging && err instanceof Error) {
|
||||||
// log the error
|
// log the error
|
||||||
logger.error(err, {
|
logger.error(err, {
|
||||||
method: req.method,
|
method: req.method,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const router = express.Router(); // eslint-disable-line new-cap
|
|||||||
|
|
||||||
const BASE_URL = nconf.get('BASE_URL');
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
|
|
||||||
router.all('*', (req, res, next) => {
|
router.all('*route', (req, res, next) => {
|
||||||
const error = new NotFound(`API v1 is no longer supported, please use API v3 instead (${BASE_URL}/apidoc).`);
|
const error = new NotFound(`API v1 is no longer supported, please use API v3 instead (${BASE_URL}/apidoc).`);
|
||||||
return next(error);
|
return next(error);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const router = express.Router(); // eslint-disable-line new-cap
|
|||||||
|
|
||||||
const BASE_URL = nconf.get('BASE_URL');
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
|
|
||||||
router.all('*', (req, res, next) => {
|
router.all('*route', (req, res, next) => {
|
||||||
const error = new NotFound(`API v2 is no longer supported, please use API v3 instead (${BASE_URL}/apidoc).`);
|
const error = new NotFound(`API v2 is no longer supported, please use API v3 instead (${BASE_URL}/apidoc).`);
|
||||||
return next(error);
|
return next(error);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user