mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-10-29 20:24:53 +01:00
Compare commits
3 Commits
phillip/se
...
phillip/ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6092cba1ea | ||
|
|
0b4adbbf07 | ||
|
|
8faa5b0582 |
@@ -1,12 +1,13 @@
|
||||
import gulp from 'gulp';
|
||||
import clean from 'rimraf';
|
||||
import { rimraf as clean } from 'rimraf';
|
||||
import apidoc from 'apidoc';
|
||||
|
||||
const APIDOC_DEST_PATH = './apidoc/html';
|
||||
const APIDOC_SRC_PATH = './website/server';
|
||||
const APIDOC_CONFIG_PATH = './apidoc/apidoc.json';
|
||||
gulp.task('apidoc:clean', done => {
|
||||
clean(APIDOC_DEST_PATH, done);
|
||||
clean.sync(APIDOC_DEST_PATH);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('apidoc', gulp.series('apidoc:clean', done => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import babel from 'gulp-babel';
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
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')
|
||||
.pipe(babel())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import gulp from 'gulp';
|
||||
import spritesmith from 'gulp.spritesmith';
|
||||
import clean from 'rimraf';
|
||||
import { rimraf as clean } from 'rimraf';
|
||||
import mergeStream from 'merge-stream';
|
||||
import { sync } from 'glob';
|
||||
|
||||
@@ -109,7 +109,8 @@ gulp.task('sprites:main', async () => {
|
||||
});
|
||||
|
||||
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()));
|
||||
|
||||
Submodule habitica-images updated: e3215f16f9...aa72332019
@@ -29,9 +29,9 @@
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-habitrpg": "^6.2.3",
|
||||
"eslint-plugin-mocha": "^5.0.0",
|
||||
"express": "^4.21.1",
|
||||
"express": "5.1.0",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"express-validator": "^5.2.0",
|
||||
"express-validator": "7.2.1",
|
||||
"firebase-admin": "^12.1.1",
|
||||
"glob": "^8.1.0",
|
||||
"got": "^11.8.6",
|
||||
@@ -66,8 +66,8 @@
|
||||
"rate-limiter-flexible": "^2.4.2",
|
||||
"redis": "^3.1.2",
|
||||
"remove-markdown": "^0.5.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^4.2.2",
|
||||
"rimraf": "6.0.1",
|
||||
"sinon": "^15.2.0",
|
||||
"stripe": "^12.18.0",
|
||||
"superagent": "^8.1.2",
|
||||
|
||||
@@ -47,6 +47,12 @@ describe('highlightMentions', () => {
|
||||
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 () => {
|
||||
const text = '@nouser message';
|
||||
const result = await highlightMentions(text);
|
||||
|
||||
@@ -238,6 +238,18 @@ describe('POST /chat', () => {
|
||||
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 () => {
|
||||
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.
|
||||
|
||||
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;
|
||||
color: $gray-200;
|
||||
opacity: 1;
|
||||
box-shadow: none;
|
||||
background-color: $gray-700;
|
||||
background-color: 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 {
|
||||
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 () {
|
||||
this.close();
|
||||
setTimeout(() => {
|
||||
this.$router.push({ name: 'achievements' });
|
||||
this.$router.push(`/profile/${this.$store.state.user.data._id}#achievements`);
|
||||
}, 200);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -72,32 +72,40 @@
|
||||
</div>
|
||||
<div class="col-12 col-md-6 text-right">
|
||||
<div
|
||||
class="box member-count"
|
||||
class="box member-count p-2"
|
||||
@click="showMemberModal()"
|
||||
>
|
||||
<div
|
||||
class="svg-icon member-icon"
|
||||
v-html="icons.memberIcon"
|
||||
></div>
|
||||
{{ challenge.memberCount }}
|
||||
<div
|
||||
v-once
|
||||
class="details"
|
||||
>
|
||||
{{ $t('participantsTitle') }}
|
||||
<div class="box-content">
|
||||
<div class="icon-number-row">
|
||||
<div
|
||||
class="svg-icon member-icon"
|
||||
v-html="icons.memberIcon"
|
||||
></div>
|
||||
<span class="number">{{ challenge.memberCount }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="details"
|
||||
>
|
||||
{{ $t('participantsTitle') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div
|
||||
class="svg-icon gem-icon"
|
||||
v-html="icons.gemIcon"
|
||||
></div>
|
||||
{{ challenge.prize || 0 }}
|
||||
<div
|
||||
v-once
|
||||
class="details"
|
||||
>
|
||||
{{ $t('prize') }}
|
||||
<div class="box prize-count p-2">
|
||||
<div class="box-content">
|
||||
<div class="icon-number-row">
|
||||
<div
|
||||
class="svg-icon gem-icon"
|
||||
v-html="icons.gemIcon"
|
||||
></div>
|
||||
<span class="number">{{ challenge.prize || 0 }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="details"
|
||||
>
|
||||
{{ $t('prize') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -304,7 +312,6 @@
|
||||
|
||||
.box {
|
||||
display: inline-block;
|
||||
padding: 1em;
|
||||
border-radius: 2px;
|
||||
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);
|
||||
@@ -314,22 +321,88 @@
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
vertical-align: bottom;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&.member-count:hover {
|
||||
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 {
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
margin-right: .2em;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.details {
|
||||
font-size: 12px;
|
||||
margin-top: 0.4em;
|
||||
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"
|
||||
:title="$t('endChallenge')"
|
||||
size="md"
|
||||
:hide-header="false"
|
||||
>
|
||||
<div
|
||||
slot="modal-header"
|
||||
@@ -15,6 +16,9 @@
|
||||
>
|
||||
{{ $t('endChallenge') }}
|
||||
</h2>
|
||||
<close-x
|
||||
@close="$root.$emit('bv::hide::modal', 'close-challenge-modal')"
|
||||
/>
|
||||
</div>
|
||||
<div class="row text-center">
|
||||
<span
|
||||
@@ -28,28 +32,67 @@
|
||||
class="col-12"
|
||||
>
|
||||
<div class="col-12">
|
||||
<div class="support-habitica">
|
||||
<!-- @TODO: Add challenge achievement badge here-->
|
||||
<div class="badge-section">
|
||||
<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 class="col-12">
|
||||
<strong v-once>{{ $t('selectChallengeWinnersDescription') }}</strong>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<member-search-dropdown
|
||||
:text="winnerText"
|
||||
:members="members"
|
||||
:challenge-id="challengeId"
|
||||
@member-selected="selectMember"
|
||||
/>
|
||||
<div class="col-12 search-input-container">
|
||||
<div class="search-input-wrapper">
|
||||
<div
|
||||
class="search-icon"
|
||||
v-html="icons.search"
|
||||
></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 class="col-12">
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-primary"
|
||||
class="btn award-winner-btn"
|
||||
:class="{'has-winner': winner._id}"
|
||||
:disabled="!winner._id"
|
||||
@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>
|
||||
</div>
|
||||
</span>
|
||||
@@ -60,14 +103,27 @@
|
||||
</div>
|
||||
</div>
|
||||
<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 class="col-12">
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-danger"
|
||||
class="btn btn-danger delete-challenge-btn"
|
||||
@click="deleteChallenge()"
|
||||
>
|
||||
<div
|
||||
class="svg-icon color delete-icon"
|
||||
v-html="icons.deleteIcon"
|
||||
></div>
|
||||
{{ $t('deleteChallenge') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -82,6 +138,7 @@
|
||||
|
||||
<style lang='scss'>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
@import '@/assets/scss/button.scss';
|
||||
|
||||
#close-challenge-modal {
|
||||
h2 {
|
||||
@@ -94,26 +151,190 @@
|
||||
|
||||
.header-wrap {
|
||||
width: 100%;
|
||||
padding-top: 2em;
|
||||
padding-top: 32px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.support-habitica {
|
||||
background-image: url('@/assets/svg/for-css/support-habitica-gems.svg?raw');
|
||||
width: 325px;
|
||||
height: 89px;
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-input-container {
|
||||
margin-top: 1em !important;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
position: relative;
|
||||
width: 384px;
|
||||
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 {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.footer-wrap {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.col-12 {
|
||||
margin-top: 2em;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.col-12:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.or {
|
||||
@@ -123,21 +344,39 @@
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
font-weight: bold;
|
||||
color: $gray-100;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<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 {
|
||||
components: {
|
||||
memberSearchDropdown,
|
||||
closeX,
|
||||
},
|
||||
props: ['challengeId', 'members', 'prize', 'flagCount'],
|
||||
data () {
|
||||
return {
|
||||
winner: {},
|
||||
searchTerm: '',
|
||||
showResults: false,
|
||||
filteredMembers: [],
|
||||
icons: Object.freeze({
|
||||
search: searchIcon,
|
||||
deleteIcon,
|
||||
gem: gemIcon,
|
||||
endChallengeBadge,
|
||||
gemsOrange,
|
||||
gemsPurple,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -150,8 +389,35 @@ export default {
|
||||
},
|
||||
},
|
||||
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) {
|
||||
this.winner = member;
|
||||
this.searchTerm = this.getMemberDisplayName(member);
|
||||
this.showResults = false;
|
||||
},
|
||||
handleBlur () {
|
||||
setTimeout(() => {
|
||||
this.showResults = false;
|
||||
}, 200);
|
||||
},
|
||||
async closeChallenge () {
|
||||
this.challenge = await this.$store.dispatch('challenges:selectChallengeWinner', {
|
||||
|
||||
@@ -1,37 +1,43 @@
|
||||
<template>
|
||||
<div
|
||||
class="notification d-flex flex-column justify-content-center text-center"
|
||||
class="notification d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<strong
|
||||
v-once
|
||||
class="mx-auto mb-2"
|
||||
<img
|
||||
src="@/assets/images/gifts_start.svg"
|
||||
class="gift-start"
|
||||
alt=""
|
||||
>
|
||||
{{ $t('g1g1') }}
|
||||
</strong>
|
||||
<small
|
||||
v-once
|
||||
class="mx-4 mb-3"
|
||||
>
|
||||
{{ $t('g1g1Details') }}
|
||||
</small>
|
||||
<div
|
||||
class="btn-secondary mx-auto d-flex"
|
||||
@click="showSelectUser()"
|
||||
>
|
||||
<div
|
||||
<div class="content-wrapper d-flex flex-column justify-content-center text-center">
|
||||
<strong
|
||||
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') }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<img
|
||||
src="@/assets/images/gifts_start.svg"
|
||||
class="gift-end"
|
||||
alt=""
|
||||
>
|
||||
<div
|
||||
class="notification-remove"
|
||||
@click.stop="remove()"
|
||||
class="close-x"
|
||||
@click="remove()"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon"
|
||||
class="svg-icon svg-close"
|
||||
v-html="icons.close"
|
||||
></div>
|
||||
</div>
|
||||
@@ -41,51 +47,89 @@
|
||||
<style lang='scss' scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
small, strong {
|
||||
small {
|
||||
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 {
|
||||
background-image: url('@/assets/images/g1g1-notif.png');
|
||||
background-image: url('@/assets/images/gifts_bg.svg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 10rem;
|
||||
padding: 3rem;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notification-remove {
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 4px;
|
||||
right: 24px;
|
||||
top: 24px;
|
||||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
.content-wrapper {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
width: 5.75rem;
|
||||
min-height: 1.5rem;
|
||||
border-radius: 2px;
|
||||
border-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
.gift-start {
|
||||
height: 96px;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
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>
|
||||
|
||||
<script>
|
||||
import closeIcon from '@/assets/svg/close-teal.svg?raw';
|
||||
import { mapActions } from '@/libs/store';
|
||||
import closeIcon from '@/assets/svg/close-white.svg?raw';
|
||||
|
||||
export default {
|
||||
props: ['notification'],
|
||||
props: ['notification', 'eventKey'],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
@@ -94,11 +138,11 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
readNotification: 'notifications:readNotification',
|
||||
}),
|
||||
remove () {
|
||||
this.readNotification({ notificationId: this.notification.id });
|
||||
if (this.eventKey) {
|
||||
window.sessionStorage.setItem(`hide-g1g1-${this.eventKey}`, 'true');
|
||||
}
|
||||
this.$emit('notification-removed');
|
||||
},
|
||||
showSelectUser () {
|
||||
this.$root.$emit('bv::show::modal', 'select-user-modal');
|
||||
|
||||
@@ -71,7 +71,7 @@ export default {
|
||||
props: ['notification', 'canRemove'],
|
||||
methods: {
|
||||
action () {
|
||||
this.$router.push({ name: 'achievements' });
|
||||
this.$router.push(`/profile/${this.$store.state.user.data._id}#achievements`);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
action () {
|
||||
this.$router.push({ name: 'stats' });
|
||||
this.$router.push(`/profile/${this.$store.state.user.data._id}#stats`);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
v-if="showOnboardingGuide"
|
||||
:never-seen="hasSpecialBadge"
|
||||
/>
|
||||
<gift-one-get-one-notification
|
||||
v-if="shouldShowG1g1"
|
||||
:notification="g1g1Notification"
|
||||
:event-key="g1g1EventKey"
|
||||
@notification-removed="handleG1g1Removed"
|
||||
/>
|
||||
<component
|
||||
:is="notification.type"
|
||||
v-for="notification in notifications"
|
||||
@@ -114,6 +120,7 @@
|
||||
<script>
|
||||
import * as quests from '@/../../common/script/content/quests';
|
||||
import { hasCompletedOnboarding } from '@/../../common/script/libs/onboarding';
|
||||
import find from 'lodash/find';
|
||||
import { mapState, mapActions } from '@/libs/store';
|
||||
import notificationsIcon from '@/assets/svg/notifications.svg?raw';
|
||||
import MenuDropdown from '../ui/customMenuDropdown';
|
||||
@@ -151,6 +158,7 @@ export default {
|
||||
CARD_RECEIVED,
|
||||
CHALLENGE_INVITATION,
|
||||
GIFT_ONE_GET_ONE,
|
||||
GiftOneGetOneNotification: GIFT_ONE_GET_ONE,
|
||||
GROUP_TASK_ASSIGNED,
|
||||
GROUP_TASK_CLAIMED,
|
||||
GROUP_TASK_NEEDS_WORK,
|
||||
@@ -178,17 +186,14 @@ export default {
|
||||
hasSpecialBadge: false,
|
||||
quests,
|
||||
openStatus: undefined,
|
||||
g1g1Hidden: false,
|
||||
actionableNotifications: [
|
||||
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_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: [
|
||||
'NEW_STUFF',
|
||||
'ITEM_RECEIVED',
|
||||
'GIFT_ONE_GET_ONE',
|
||||
'GROUP_TASK_NEEDS_WORK',
|
||||
'GUILD_INVITATION',
|
||||
'PARTY_INVITATION',
|
||||
@@ -207,7 +212,10 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
notificationsOrder () {
|
||||
// Returns a map of NOTIFICATION_TYPE -> POSITION
|
||||
const orderMap = {};
|
||||
@@ -286,9 +294,9 @@ export default {
|
||||
|
||||
return notifications;
|
||||
},
|
||||
// The total number of notification, shown inside the dropdown
|
||||
notificationsCount () {
|
||||
return this.notifications.length;
|
||||
const g1g1Count = this.shouldShowG1g1 ? 1 : 0;
|
||||
return this.notifications.length + g1g1Count;
|
||||
},
|
||||
hasUnseenNotifications () {
|
||||
return this.notifications.some(notification => (notification.seen === false));
|
||||
@@ -299,6 +307,30 @@ export default {
|
||||
showOnboardingGuide () {
|
||||
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 () {
|
||||
const onboardingPanelState = getLocalSetting(CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE);
|
||||
@@ -364,6 +396,9 @@ export default {
|
||||
isActionable (notification) {
|
||||
return this.actionableNotifications.indexOf(notification.type) !== -1;
|
||||
},
|
||||
handleG1g1Removed () {
|
||||
this.g1g1Hidden = true;
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
@@ -176,7 +176,12 @@ export default {
|
||||
}
|
||||
},
|
||||
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 () {
|
||||
this.$router.push({ name: 'subscription' });
|
||||
|
||||
@@ -454,17 +454,14 @@ export default {
|
||||
},
|
||||
isUserMentioned () {
|
||||
const message = this.msg;
|
||||
|
||||
if (message.highlight) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { user } = this;
|
||||
const displayName = user.profile.name;
|
||||
const { username } = user.auth.local;
|
||||
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
|
||||
message.highlight = new RegExp(pattern, 'i').test(message.text);
|
||||
|
||||
if (!username) return false;
|
||||
const usernamePattern = new RegExp(`@${escapeRegExp(username)}(?:\\b|(?=[^a-zA-Z0-9_]))`, 'i');
|
||||
message.highlight = usernamePattern.test(message.text);
|
||||
return message.highlight;
|
||||
},
|
||||
flagCountDescription () {
|
||||
|
||||
@@ -1126,7 +1126,12 @@ export default {
|
||||
this.loadUser();
|
||||
this.oldTitle = this.$store.state.title;
|
||||
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.loadUser();
|
||||
});
|
||||
@@ -1211,10 +1216,15 @@ export default {
|
||||
},
|
||||
selectPage (page) {
|
||||
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', {
|
||||
section: this.$t('user'),
|
||||
subSection: this.$t(this.startingPage),
|
||||
subSection: this.$t(page),
|
||||
});
|
||||
},
|
||||
getNextIncentive () {
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import habiticaMarkdown from 'habitica-markdown/withMentions';
|
||||
import escapeRegExp from 'lodash/escapeRegExp';
|
||||
|
||||
export default function renderWithMentions (text, user) {
|
||||
if (!text) return null;
|
||||
const env = { userName: user.auth.local.username, displayName: user.profile.name };
|
||||
return habiticaMarkdown.render(String(text), env);
|
||||
const env = { userName: user.auth.local.username };
|
||||
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',
|
||||
props: true,
|
||||
},
|
||||
{ name: 'profile', path: '/user/profile' },
|
||||
{ name: 'stats', path: '/user/stats' },
|
||||
{ name: 'achievements', path: '/user/achievements' },
|
||||
{
|
||||
path: '/inventory',
|
||||
component: InventoryContainer,
|
||||
@@ -369,6 +372,10 @@ router.beforeEach(async (to, from, next) => {
|
||||
if (to.params.startingPage !== undefined) {
|
||||
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) {
|
||||
store.state.postLoadModal = `profile/${to.params.userId}`;
|
||||
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) {
|
||||
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', {
|
||||
userId,
|
||||
startingPage: to.name,
|
||||
fromPath: from.path,
|
||||
toPath: to.path,
|
||||
toPath: redirectPath,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ describe('renderWithMentions', () => {
|
||||
expect(result).to.be.null;
|
||||
});
|
||||
|
||||
test('highlights displayname', () => {
|
||||
test('does not highlight displayname to prevent impersonation', () => {
|
||||
const text = 'hello @displayedUser with text after';
|
||||
|
||||
const result = renderMarkdown(text, user('user', 'displayedUser'));
|
||||
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@displayedUser</span>');
|
||||
expect(result).to.contain('<span class="at-text">@displayedUser</span>');
|
||||
expect(result).to.not.contain('<span class="at-text at-highlight">@displayedUser</span>');
|
||||
});
|
||||
|
||||
test('highlights username', () => {
|
||||
@@ -56,7 +56,8 @@ describe('renderWithMentions', () => {
|
||||
|
||||
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">@mail</span>');
|
||||
expect(result).to.not.contain('<span class="at-text at-highlight">@mentions</span>.com');
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"awardWinners": "Award Winner",
|
||||
"doYouWantedToDeleteChallenge": "Do you want to delete this 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?",
|
||||
"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!",
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"notEnoughGems": "Not enough Gems",
|
||||
"alreadyHave": "Whoops! You already have this item. No need to buy it again!",
|
||||
"delete": "Delete",
|
||||
"gem": "Gem",
|
||||
"gems": "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!",
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
"needsTextPlaceholder": "Type your message here.",
|
||||
"messageCopiedToClipboard": "Message copied to clipboard.",
|
||||
"leaderOnlyChallenges": "Only group leader can create challenges",
|
||||
"sendGift": "Send a Gift",
|
||||
"sendGift": "Send Gift",
|
||||
"selectGift": "Select Gift",
|
||||
"selectSubscription": "Select Subscription",
|
||||
"sendGiftToWhom": "Who would you like to send a gift to?",
|
||||
|
||||
@@ -3,6 +3,7 @@ import moment from 'moment';
|
||||
import pick from 'lodash/pick';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import nconf from 'nconf';
|
||||
import { body , validationResult } from 'express-validator';
|
||||
import {
|
||||
authWithHeaders,
|
||||
} from '../../middlewares/auth';
|
||||
@@ -87,8 +88,8 @@ api.loginLocal = {
|
||||
errorMessage: res.t('missingPassword'),
|
||||
},
|
||||
});
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
req.sanitizeBody('username').trim();
|
||||
req.sanitizeBody('password').trim();
|
||||
@@ -216,8 +217,8 @@ api.updateUsername = {
|
||||
},
|
||||
});
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const newUsername = req.body.username;
|
||||
|
||||
@@ -307,7 +308,7 @@ api.updatePassword = {
|
||||
},
|
||||
});
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
const validationErrors = validationResult(req).array();
|
||||
|
||||
if (validationErrors) {
|
||||
throw validationErrors;
|
||||
@@ -353,8 +354,8 @@ api.resetPassword = {
|
||||
notEmpty: { errorMessage: res.t('missingEmail') },
|
||||
},
|
||||
});
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const email = req.body.email.toLowerCase();
|
||||
let user = await User.findOne(
|
||||
@@ -419,12 +420,12 @@ api.updateEmail = {
|
||||
|
||||
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) {
|
||||
req.checkBody('password', res.t('missingPassword')).notEmpty();
|
||||
await body('password', res.t('missingPassword')).notEmpty().run(req)
|
||||
}
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const emailAlreadyInUse = await User.findOne({
|
||||
'auth.local.email': req.body.newEmail.toLowerCase(),
|
||||
@@ -485,8 +486,8 @@ api.resetPasswordSetNewOne = {
|
||||
},
|
||||
});
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { newPassword, confirmPassword } = req.body;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import merge from 'lodash/merge';
|
||||
import pick from 'lodash/pick';
|
||||
import reduce from 'lodash/reduce';
|
||||
import times from 'lodash/times';
|
||||
import { body, param, query , validationResult } from 'express-validator';
|
||||
import { authWithHeaders, authWithSession } from '../../middlewares/auth';
|
||||
import { model as Challenge } from '../../models/challenge';
|
||||
import bannedWords from '../../libs/bannedWords';
|
||||
@@ -232,11 +233,11 @@ api.createChallenge = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkBody('group', apiError('groupIdRequired')).notEmpty();
|
||||
req.checkBody('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_CHALLENGES });
|
||||
await body('group', apiError('groupIdRequired')).notEmpty().run(req)
|
||||
await body('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_CHALLENGES }).run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const group = await Group.getGroup({
|
||||
user, groupId: req.body.group, fields: basicGroupFields, optionalMembership: true,
|
||||
@@ -328,10 +329,10 @@ api.joinChallenge = {
|
||||
async handler (req, res) {
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
@@ -397,10 +398,10 @@ api.leaveChallenge = {
|
||||
const { user } = res.locals;
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
@@ -463,10 +464,10 @@ api.getUserChallenges = {
|
||||
url: '/challenges/user',
|
||||
middlewares: [authWithHeaders()],
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const CHALLENGES_PER_PAGE = 10;
|
||||
const {
|
||||
@@ -599,10 +600,10 @@ api.getGroupChallenges = {
|
||||
const { user } = res.locals;
|
||||
let { groupId } = req.params;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req);
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
if (groupId === 'party') groupId = user.party._id;
|
||||
if (groupId === 'habitrpg') groupId = TAVERN_ID;
|
||||
@@ -661,10 +662,10 @@ api.getChallenge = {
|
||||
url: '/challenges/:challengeId',
|
||||
middlewares: [authWithHeaders()],
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const { challengeId } = req.params;
|
||||
@@ -719,10 +720,10 @@ api.exportChallengeCsv = {
|
||||
url: '/challenges/:challengeId/export/csv',
|
||||
middlewares: [authWithSession],
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const { challengeId } = req.params;
|
||||
@@ -836,11 +837,11 @@ api.updateChallenge = {
|
||||
url: '/challenges/:challengeId',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
||||
req.checkBody('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_CHALLENGES });
|
||||
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||
await body('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_CHALLENGES }).run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const { challengeId } = req.params;
|
||||
@@ -883,10 +884,10 @@ api.deleteChallenge = {
|
||||
async handler (req, res) {
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
@@ -931,11 +932,11 @@ api.selectChallengeWinner = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('winnerId', res.t('winnerIdRequired')).notEmpty().isUUID();
|
||||
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||
await param('winnerId', res.t('winnerIdRequired')).notEmpty().isUUID().run(req);
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
@@ -992,10 +993,10 @@ api.cloneChallenge = {
|
||||
async handler (req, res) {
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const challengeToClone = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||
if (!challengeToClone) throw new NotFound(res.t('challengeNotFound'));
|
||||
@@ -1061,10 +1062,10 @@ api.flagChallenge = {
|
||||
async handler (req, res) {
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
@@ -1094,10 +1095,10 @@ api.clearFlagsChallenge = {
|
||||
async handler (req, res) {
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
if (!user.hasPermission('moderator')) {
|
||||
throw new NotAuthorized(res.t('messageGroupChatAdminClearFlagCount'));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import couponCode from 'coupon-code';
|
||||
import { param, query , validationResult } from 'express-validator';
|
||||
import csvStringify from '../../libs/csvStringify';
|
||||
import {
|
||||
authWithHeaders,
|
||||
@@ -72,11 +73,11 @@ api.generateCoupons = {
|
||||
url: '/coupons/generate/:event',
|
||||
middlewares: [authWithHeaders(), ensurePermission('coupons')],
|
||||
async handler (req, res) {
|
||||
req.checkParams('event', apiError('eventRequired')).notEmpty();
|
||||
req.checkQuery('count', apiError('countRequired')).notEmpty().isNumeric();
|
||||
await param('event', apiError('eventRequired')).notEmpty().run(req)
|
||||
await query('count', apiError('countRequired')).notEmpty().isNumeric().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const coupons = await Coupon.generate(req.params.event, req.query.count);
|
||||
res.respond(200, coupons);
|
||||
@@ -122,10 +123,10 @@ api.validateCoupon = {
|
||||
optional: true,
|
||||
})],
|
||||
async handler (req, res) {
|
||||
req.checkParams('code', res.t('couponCodeRequired')).notEmpty();
|
||||
await param('code', res.t('couponCodeRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
let valid = false;
|
||||
const code = couponCode.validate(req.params.code);
|
||||
|
||||
@@ -9,6 +9,7 @@ import pick from 'lodash/pick';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import nconf from 'nconf';
|
||||
import moment from 'moment';
|
||||
import { body, param, query , validationResult } from 'express-validator';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import {
|
||||
model as Group,
|
||||
@@ -132,10 +133,10 @@ api.createGroup = {
|
||||
const group = new Group(Group.sanitize(req.body));
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
if (group.type === 'guild') {
|
||||
if (!user.hasPermission('fullAccess')) {
|
||||
@@ -206,10 +207,10 @@ api.createGroupPlan = {
|
||||
const { user } = res.locals;
|
||||
const group = new Group(Group.sanitize(req.body.groupToCreate));
|
||||
|
||||
req.checkBody('paymentType', res.t('paymentTypeRequired')).notEmpty();
|
||||
req.checkBody('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_GUILDS });
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
await body('paymentType', res.t('paymentTypeRequired')).notEmpty().run(req)
|
||||
await body('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_GUILDS }).run(req)
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
// @TODO: Change message
|
||||
if (group.privacy !== 'private') throw new NotAuthorized(res.t('partyMustbePrivate'));
|
||||
@@ -321,13 +322,13 @@ api.getGroups = {
|
||||
async handler (req, res) {
|
||||
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
|
||||
req.checkQuery('paginate').optional().isIn(['true', 'false'], apiError('guildsPaginateBooleanString'));
|
||||
req.checkQuery('page').optional().isInt({ min: 0 }, apiError('queryPageInteger'));
|
||||
await query('paginate').optional().isIn(['true', 'false'], apiError('guildsPaginateBooleanString')).run(req)
|
||||
await query('page').optional().isInt({ min: 0 }, apiError('queryPageInteger')).run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const types = req.query.type.split(',');
|
||||
|
||||
@@ -419,10 +420,10 @@ api.getGroup = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req);
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { groupId } = req.params;
|
||||
const group = await Group.getGroup({ user, groupId, populateLeader: false });
|
||||
@@ -481,11 +482,11 @@ api.updateGroup = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
req.checkBody('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_GUILDS });
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req);
|
||||
await body('summary', apiError('summaryLengthExceedsMax')).isLength({ max: MAX_SUMMARY_SIZE_FOR_GUILDS }).run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
const optionalMembership = Boolean(user.hasPermission('moderator'));
|
||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, optionalMembership });
|
||||
|
||||
@@ -567,10 +568,10 @@ api.joinGroup = {
|
||||
const { user } = res.locals;
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
// 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
|
||||
@@ -760,10 +761,10 @@ api.rejectGroupInvite = {
|
||||
async handler (req, res) {
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { groupId } = req.params;
|
||||
let isUserInvited = false;
|
||||
@@ -831,13 +832,13 @@ api.leaveGroup = {
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
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?
|
||||
req.checkQuery('keep', apiError('keepOrRemoveAll')).optional().isIn(['keep-all', 'remove-all']);
|
||||
req.checkBody('keepChallenges', apiError('groupRemainOrLeaveChallenges')).optional().isIn(['remain-in-challenges', 'leave-challenges']);
|
||||
await query('keep', apiError('keepOrRemoveAll')).optional().isIn(['keep-all', 'remove-all']).run(req)
|
||||
await body('keepChallenges', apiError('groupRemainOrLeaveChallenges')).optional().isIn(['remain-in-challenges', 'leave-challenges']).run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { groupId } = req.params;
|
||||
await leaveGroup({
|
||||
@@ -899,11 +900,11 @@ api.removeGroupMember = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
req.checkParams('memberId', res.t('userIdRequired')).notEmpty().isUUID();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req);
|
||||
await param('memberId', res.t('userIdRequired')).notEmpty().isUUID().run(req);
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
const optionalMembership = Boolean(user.hasPermission('moderator'));
|
||||
const group = await Group.getGroup({
|
||||
user, groupId: req.params.groupId, optionalMembership, fields: '-chat',
|
||||
@@ -1105,12 +1106,12 @@ api.inviteToGroup = {
|
||||
|
||||
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 }));
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: '-chat' });
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
@@ -1182,11 +1183,11 @@ api.addGroupManager = {
|
||||
const { user } = res.locals;
|
||||
const { managerId } = req.body;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty(); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
||||
req.checkBody('managerId', apiError('managerIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
||||
await body('managerId', apiError('managerIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const newManager = await User.findById(managerId, 'guilds party').exec();
|
||||
const groupFields = basicGroupFields.concat(' managers');
|
||||
@@ -1232,11 +1233,11 @@ api.removeGroupManager = {
|
||||
const { user } = res.locals;
|
||||
const { managerId } = req.body;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty(); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
||||
req.checkBody('managerId', apiError('managerIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req); // .isUUID(); can't be used because it would block 'habitrpg' or 'party'
|
||||
await body('managerId', apiError('managerIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const groupFields = basicGroupFields.concat(' managers');
|
||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: groupFields });
|
||||
@@ -1323,7 +1324,7 @@ api.getLookingForParty = {
|
||||
const USERS_PER_PAGE = 30;
|
||||
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_START = USERS_PER_PAGE * PAGE;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import validator from 'validator';
|
||||
import { query, param , validationResult } from 'express-validator';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import { ensurePermission } from '../../middlewares/ensureAccessRight';
|
||||
import { model as User } from '../../models/user';
|
||||
@@ -73,10 +74,10 @@ api.getPatrons = {
|
||||
url: '/hall/patrons',
|
||||
middlewares: [authWithHeaders()],
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const page = req.query.page ? Number(req.query.page) : 0;
|
||||
const perPage = 50;
|
||||
@@ -177,10 +178,10 @@ api.getHero = {
|
||||
url: '/hall/heroes/:heroId',
|
||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||
async handler (req, res) {
|
||||
req.checkParams('heroId', res.t('heroIdRequired')).notEmpty();
|
||||
await param('heroId', res.t('heroIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { heroId } = req.params;
|
||||
|
||||
@@ -263,10 +264,10 @@ api.updateHero = {
|
||||
const { heroId } = req.params;
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const hero = await User.findById(heroId).exec();
|
||||
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',
|
||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||
async handler (req, res) {
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty().isUUID();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().isUUID().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { groupId } = req.params;
|
||||
|
||||
@@ -597,10 +598,10 @@ api.getHeroGroupPlans = {
|
||||
url: '/hall/heroes/:heroId/group-plans',
|
||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||
async handler (req, res) {
|
||||
req.checkParams('heroId', res.t('heroIdRequired')).notEmpty();
|
||||
await param('heroId', res.t('heroIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { heroId } = req.params;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import escapeRegExp from 'lodash/escapeRegExp';
|
||||
import pick from 'lodash/pick';
|
||||
import { body, param, query as checkQuery , validationResult } from 'express-validator';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import {
|
||||
model as User,
|
||||
@@ -103,10 +104,10 @@ api.getMember = {
|
||||
url: '/members/:memberId',
|
||||
middlewares: [authWithHeaders()],
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { memberId } = req.params;
|
||||
|
||||
@@ -132,10 +133,10 @@ api.getMemberByUsername = {
|
||||
url: '/members/username/:username',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('username', res.t('invalidReqParams')).notEmpty();
|
||||
await param('username', res.t('invalidReqParams')).notEmpty().run(req);
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
let username = req.params.username.toLowerCase();
|
||||
if (username[0] === '@') username = username.slice(1, username.length);
|
||||
@@ -261,10 +262,10 @@ api.getMemberAchievements = {
|
||||
url: '/members/:memberId/achievements',
|
||||
middlewares: [authWithHeaders()],
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { memberId } = req.params;
|
||||
|
||||
@@ -292,13 +293,13 @@ function _getMembersForItem (type) {
|
||||
}
|
||||
|
||||
return async function handleGetMembersForItem (req, res) {
|
||||
req.checkParams('groupId', res.t('groupIdRequired')).notEmpty();
|
||||
req.checkQuery('lastId').optional().notEmpty().isUUID();
|
||||
await param('groupId', res.t('groupIdRequired')).notEmpty().run(req);
|
||||
await checkQuery('lastId').optional().notEmpty().isUUID().run(req)
|
||||
// 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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { groupId } = req.params;
|
||||
const { lastId } = req.query;
|
||||
@@ -566,11 +567,11 @@ api.getChallengeMemberProgress = {
|
||||
url: '/challenges/:challengeId/members/:memberId',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
||||
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||
await param('memberId', res.t('memberIdRequired')).notEmpty().isUUID().run(req);
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const { challengeId } = req.params;
|
||||
@@ -627,11 +628,11 @@ api.getObjectionsToInteraction = {
|
||||
url: '/members/:toUserId/objections/:interaction',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID();
|
||||
req.checkParams('interaction', res.t('interactionRequired')).notEmpty().isIn(KNOWN_INTERACTIONS);
|
||||
await param('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID().run(req);
|
||||
await param('interaction', res.t('interactionRequired')).notEmpty().isIn(KNOWN_INTERACTIONS).run(req);
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const sender = res.locals.user;
|
||||
const receiver = await User.findById(req.params.toUserId).exec();
|
||||
@@ -662,11 +663,11 @@ api.transferGems = {
|
||||
url: '/members/transfer-gems',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkBody('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID();
|
||||
req.checkBody('gemAmount', res.t('gemAmountRequired')).notEmpty().isInt();
|
||||
await body('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID().run(req)
|
||||
await body('gemAmount', res.t('gemAmountRequired')).notEmpty().isInt().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const sender = res.locals.user;
|
||||
const receiver = await User.findById(req.body.toUserId).exec();
|
||||
@@ -815,9 +816,9 @@ api.clearUserFlags = {
|
||||
const { user } = res.locals;
|
||||
const { memberId } = req.params;
|
||||
|
||||
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
await param('memberId', res.t('memberIdRequired')).notEmpty().isUUID().run(req);
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
if (!user.hasPermission('moderator')) {
|
||||
throw new BadRequest('Only a moderator may clear reports from a profile.');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { param , validationResult } from 'express-validator';
|
||||
|
||||
const api = {};
|
||||
|
||||
@@ -36,10 +37,10 @@ api.getModelPaths = {
|
||||
method: 'GET',
|
||||
url: '/models/:model/paths',
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
let { model } = req.params;
|
||||
// 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 {
|
||||
NotFound,
|
||||
@@ -25,11 +26,11 @@ api.addPushDevice = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkBody('regId', res.t('regIdRequired')).notEmpty();
|
||||
req.checkBody('type', res.t('typeRequired')).notEmpty().isIn(['ios', 'android']);
|
||||
await body('regId', res.t('regIdRequired')).notEmpty().run(req)
|
||||
await body('type', res.t('typeRequired')).notEmpty().isIn(['ios', 'android']).run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { pushDevices } = user;
|
||||
|
||||
@@ -75,10 +76,10 @@ api.removePushDevice = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('regId', res.t('regIdRequired')).notEmpty();
|
||||
await param('regId', res.t('regIdRequired')).notEmpty().run(req);
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { regId } = req.params;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import each from 'lodash/each';
|
||||
import every from 'lodash/every';
|
||||
import isBoolean from 'lodash/isBoolean';
|
||||
import pick from 'lodash/pick';
|
||||
import { param , validationResult } from 'express-validator';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import { getAnalyticsServiceByEnvironment } from '../../libs/analyticsService';
|
||||
import {
|
||||
@@ -68,10 +69,10 @@ api.inviteToQuest = {
|
||||
const { questKey } = req.params;
|
||||
const quest = questScrolls[questKey];
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
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) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
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) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: basicGroupFields.concat(' quest chat') });
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
@@ -335,10 +336,10 @@ api.forceStart = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
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 { groupId } = req.params;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const group = await Group.getGroup({ user, groupId, fields: basicGroupFields.concat(' quest') });
|
||||
|
||||
@@ -475,10 +476,10 @@ api.abortQuest = {
|
||||
const { user } = res.locals;
|
||||
const { groupId } = req.params;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const group = await Group.getGroup({ user, groupId, fields: basicGroupFields.concat(' quest chat') });
|
||||
|
||||
@@ -549,10 +550,10 @@ api.leaveQuest = {
|
||||
const { user } = res.locals;
|
||||
const { groupId } = req.params;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
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 merge from 'lodash/merge';
|
||||
import pick from 'lodash/pick';
|
||||
import { param, query , validationResult } from 'express-validator'
|
||||
import moment from 'moment';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import {
|
||||
@@ -310,9 +311,9 @@ api.createChallengeTasks = {
|
||||
url: '/tasks/challenge/:challengeId',
|
||||
middlewares: [authWithHeaders()],
|
||||
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;
|
||||
|
||||
const { user } = res.locals;
|
||||
@@ -394,10 +395,10 @@ api.getUserTasks = {
|
||||
async handler (req, res) {
|
||||
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
|
||||
req.checkQuery('type', res.t('invalidTasksTypeExtra')).optional().isIn(types);
|
||||
await query('type', res.t('invalidTasksTypeExtra')).optional().isIn(types).run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const { dueDate } = req.query;
|
||||
@@ -447,12 +448,12 @@ api.getChallengeTasks = {
|
||||
url: '/tasks/challenge/:challengeId',
|
||||
middlewares: [authWithHeaders()],
|
||||
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`);
|
||||
req.checkQuery('type', res.t('invalidTasksType')).optional().isIn(types);
|
||||
await query('type', res.t('invalidTasksType')).optional().isIn(types).run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const { challengeId } = req.params;
|
||||
@@ -602,10 +603,10 @@ api.updateTask = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
||||
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { taskId } = req.params;
|
||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||
@@ -805,11 +806,11 @@ api.moveTask = {
|
||||
url: '/tasks/:taskId/move/to/:position',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
||||
req.checkParams('position', res.t('positionRequired')).notEmpty().isNumeric();
|
||||
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||
await param('position', res.t('positionRequired')).notEmpty().isNumeric().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const { taskId } = req.params;
|
||||
@@ -908,10 +909,10 @@ api.addChecklistItem = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
||||
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { taskId } = req.params;
|
||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||
@@ -957,11 +958,11 @@ api.scoreCheckListItem = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
||||
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||
await param('itemId', res.t('itemIdRequired')).notEmpty().isUUID().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { taskId } = req.params;
|
||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||
@@ -1018,11 +1019,11 @@ api.updateChecklistItem = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
||||
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||
await param('itemId', res.t('itemIdRequired')).notEmpty().isUUID().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { taskId } = req.params;
|
||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||
@@ -1079,11 +1080,11 @@ api.removeChecklistItem = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
||||
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||
await param('itemId', res.t('itemIdRequired')).notEmpty().isUUID().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { taskId } = req.params;
|
||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||
@@ -1141,12 +1142,12 @@ api.addTagToTask = {
|
||||
async handler (req, res) {
|
||||
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);
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { taskId } = req.params;
|
||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id, { userId: user._id });
|
||||
@@ -1198,11 +1199,11 @@ api.removeTagFromTask = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
||||
req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID();
|
||||
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||
await param('tagId', res.t('tagIdRequired')).notEmpty().isUUID().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { taskId } = req.params;
|
||||
const task = await Tasks.Task.findByIdOrAlias(taskId, user._id, { userId: user._id });
|
||||
@@ -1243,11 +1244,11 @@ api.unlinkAllTasks = {
|
||||
url: '/tasks/unlink-all/:challengeId',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
||||
req.checkQuery('keep', apiError('keepOrRemoveAll')).notEmpty().isIn(['keep-all', 'remove-all']);
|
||||
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req)
|
||||
await query('keep', apiError('keepOrRemoveAll')).notEmpty().isIn(['keep-all', 'remove-all']).run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const { keep } = req.query;
|
||||
@@ -1309,11 +1310,11 @@ api.unlinkOneTask = {
|
||||
url: '/tasks/unlink-one/:taskId',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkQuery('keep', apiError('keepOrRemove')).notEmpty().isIn(['keep', 'remove']);
|
||||
await param('taskId', apiError('taskIdRequired')).notEmpty().isUUID().run(req)
|
||||
await query('keep', apiError('keepOrRemove')).notEmpty().isIn(['keep', 'remove']).run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const { keep } = req.query;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pick from 'lodash/pick';
|
||||
import isUUID from 'validator/lib/isUUID';
|
||||
import { param , query , validationResult } from 'express-validator';
|
||||
import { authWithHeaders } from '../../../middlewares/auth';
|
||||
import * as Tasks from '../../../models/task';
|
||||
import { model as Group } from '../../../models/group';
|
||||
@@ -45,9 +46,9 @@ api.createGroupTasks = {
|
||||
url: '/tasks/group/:groupId',
|
||||
middlewares: [authWithHeaders()],
|
||||
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;
|
||||
|
||||
const { user } = res.locals;
|
||||
@@ -92,11 +93,11 @@ api.getGroupTasks = {
|
||||
url: '/tasks/group/:groupId',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty().isUUID();
|
||||
req.checkQuery('type', res.t('invalidTasksType')).optional().isIn(types);
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().isUUID().run(req)
|
||||
await query('type', res.t('invalidTasksType')).optional().isIn(types).run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
|
||||
@@ -132,10 +133,10 @@ api.groupMoveTask = {
|
||||
url: '/group-tasks/:taskId/move/to/:position',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty();
|
||||
req.checkParams('position', res.t('positionRequired')).notEmpty().isNumeric();
|
||||
await param('taskId', apiError('taskIdRequired')).notEmpty().run(req)
|
||||
await param('position', res.t('positionRequired')).notEmpty().isNumeric().run(req)
|
||||
|
||||
const reqValidationErrors = req.validationErrors();
|
||||
const reqValidationErrors = validationResult(req).array();
|
||||
if (reqValidationErrors) throw reqValidationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
@@ -203,9 +204,9 @@ api.assignTask = {
|
||||
url: '/tasks/:taskId/assign',
|
||||
middlewares: [authWithHeaders()],
|
||||
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;
|
||||
|
||||
const { user } = res.locals;
|
||||
@@ -280,10 +281,10 @@ api.unassignTask = {
|
||||
url: '/tasks/:taskId/unassign/:assignedUserId',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('assignedUserId', res.t('userIdRequired')).notEmpty().isUUID();
|
||||
await param('taskId', apiError('taskIdRequired')).notEmpty().isUUID().run(req)
|
||||
await param('assignedUserId', res.t('userIdRequired')).notEmpty().isUUID().run(req)
|
||||
|
||||
const reqValidationErrors = req.validationErrors();
|
||||
const reqValidationErrors = validationResult(req).array();
|
||||
if (reqValidationErrors) throw reqValidationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
@@ -337,10 +338,10 @@ api.taskNeedsWork = {
|
||||
url: '/tasks/:taskId/needs-work/:userId',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('taskId', apiError('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('userId', res.t('userIdRequired')).notEmpty().isUUID();
|
||||
await param('taskId', apiError('taskIdRequired')).notEmpty().isUUID().run(req)
|
||||
await param('userId', res.t('userIdRequired')).notEmpty().isUUID().run(req)
|
||||
|
||||
const reqValidationErrors = req.validationErrors();
|
||||
const reqValidationErrors = validationResult(req).array();
|
||||
if (reqValidationErrors) throw reqValidationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
|
||||
@@ -4,6 +4,7 @@ import isFunction from 'lodash/isFunction';
|
||||
import pick from 'lodash/pick';
|
||||
import nconf from 'nconf';
|
||||
import get from 'lodash/get';
|
||||
import { param , validationResult } from 'express-validator';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import common from '../../../common';
|
||||
import {
|
||||
@@ -1735,11 +1736,11 @@ api.movePinnedItem = {
|
||||
url: '/user/move-pinned-item/:path/move/to/:position',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('path', res.t('taskIdRequired')).notEmpty();
|
||||
req.checkParams('position', res.t('positionRequired')).notEmpty().isNumeric();
|
||||
await param('path', res.t('taskIdRequired')).notEmpty().run(req)
|
||||
await param('position', res.t('positionRequired')).notEmpty().isNumeric().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const { path } = req.params;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import validator from 'validator';
|
||||
import merge from 'lodash/merge';
|
||||
import { param , validationResult } from 'express-validator';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
@@ -41,10 +42,10 @@ api.searchHero = {
|
||||
url: '/admin/search/:userIdentifier',
|
||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||
async handler (req, res) {
|
||||
req.checkParams('userIdentifier', res.t('userIdentifierRequired')).notEmpty();
|
||||
await param('userIdentifier', res.t('userIdentifierRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { userIdentifier } = req.params;
|
||||
|
||||
@@ -109,10 +110,10 @@ api.getUserHistory = {
|
||||
url: '/admin/user/:userId/history',
|
||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { userId } = req.params;
|
||||
|
||||
@@ -161,10 +162,10 @@ api.updateBlocker = {
|
||||
url: '/admin/blockers/:blockerId',
|
||||
middlewares: [authWithHeaders(), ensurePermission('accessControl')],
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const blocker = await Blocker.findById(req.params.blockerId).exec();
|
||||
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
|
||||
@@ -181,10 +182,10 @@ api.deleteBlocker = {
|
||||
url: '/admin/blockers/:blockerId',
|
||||
middlewares: [authWithHeaders(), ensurePermission('accessControl')],
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const blocker = await Blocker.findById(req.params.blockerId).exec();
|
||||
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
|
||||
|
||||
@@ -21,8 +21,8 @@ api.verifyUsername = {
|
||||
},
|
||||
});
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const chosenUsername = req.body.username;
|
||||
@@ -105,8 +105,8 @@ api.checkEmail = {
|
||||
},
|
||||
});
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const lowercaseEmail = req.body.email.toLowerCase();
|
||||
if (isRestrictedEmailDomain(lowercaseEmail)) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { query , validationResult } from 'express-validator';
|
||||
import { langCodes } from '../../libs/i18n';
|
||||
import { apiError } from '../../libs/apiError';
|
||||
import common from '../../../common';
|
||||
@@ -57,10 +58,10 @@ api.faq = {
|
||||
method: 'GET',
|
||||
url: '/faq',
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const proposedLang = req.query.language && req.query.language.toString();
|
||||
const language = langCodes.includes(proposedLang) ? proposedLang : 'en';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { param , validationResult } from 'express-validator';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import { apiError } from '../../libs/apiError';
|
||||
import { NotFound } from '../../libs/errors';
|
||||
@@ -38,10 +39,10 @@ api.deleteMessage = {
|
||||
middlewares: [authWithHeaders()],
|
||||
url: '/inbox/messages/:messageId',
|
||||
async handler (req, res) {
|
||||
req.checkParams('messageId', apiError('messageIdRequired')).notEmpty().isUUID();
|
||||
await param('messageId', apiError('messageIdRequired')).notEmpty().isUUID().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { messageId } = req.params;
|
||||
const { user } = res.locals;
|
||||
@@ -216,10 +217,10 @@ api.likePrivateMessage = {
|
||||
url: '/inbox/like-private-message/:uniqueMessageId',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('uniqueMessageId', apiError('messageIdRequired')).notEmpty();
|
||||
await param('uniqueMessageId', apiError('messageIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { user } = res.locals;
|
||||
const { uniqueMessageId } = req.params;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { sendJob } from '../../libs/worker';
|
||||
import { param , validationResult } from 'express-validator';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import { ensurePermission } from '../../middlewares/ensureAccessRight';
|
||||
import { TransactionModel as Transaction } from '../../models/transaction';
|
||||
@@ -16,9 +17,9 @@ api.purchaseHistory = {
|
||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||
url: '/members/:memberId/purchase-history',
|
||||
async handler (req, res) {
|
||||
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
await param('memberId', res.t('memberIdRequired')).notEmpty().isUUID().run(req)
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
let transactions = await Transaction
|
||||
.find({ userId: req.params.memberId })
|
||||
.sort({ createdAt: -1 })
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { param , validationResult } from 'express-validator';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import { apiError } from '../../libs/apiError';
|
||||
import { model as NewsPost } from '../../models/newsPost';
|
||||
@@ -110,7 +111,7 @@ api.getPost = {
|
||||
})],
|
||||
noLanguage: true,
|
||||
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 newsPost = await NewsPost.findById(req.params.postId).exec();
|
||||
@@ -148,9 +149,9 @@ api.updateNews = {
|
||||
url: '/news/:postId',
|
||||
middlewares: [authWithHeaders(), ensurePermission('news')],
|
||||
async handler (req, res) {
|
||||
req.checkParams('postId', apiError('postIdRequired')).notEmpty().isUUID();
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
await param('postId', apiError('postIdRequired')).notEmpty().isUUID().run(req)
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const newsPost = await NewsPost.findById(req.params.postId).exec();
|
||||
if (!newsPost) throw new NotFound(res.t('newsPostNotFound'));
|
||||
@@ -183,9 +184,9 @@ api.deleteNews = {
|
||||
url: '/news/:postId',
|
||||
middlewares: [authWithHeaders(), ensurePermission('news')],
|
||||
async handler (req, res) {
|
||||
req.checkParams('postId', apiError('postIdRequired')).notEmpty().isUUID();
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
await param('postId', apiError('postIdRequired')).notEmpty().isUUID().run(req)
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const newsPost = await NewsPost.findById(req.params.postId).exec();
|
||||
if (!newsPost) throw new NotFound(res.t('newsPostNotFound'));
|
||||
|
||||
@@ -252,8 +252,8 @@ api.verifyDisplayName = {
|
||||
},
|
||||
});
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const chosenDisplayName = req.body.displayName;
|
||||
|
||||
|
||||
@@ -177,10 +177,10 @@ api.exportUserAvatarHtml = {
|
||||
async handler (/* req, res */) {
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { memberId } = req.params;
|
||||
|
||||
@@ -215,10 +215,10 @@ api.exportUserAvatarPng = {
|
||||
async handler (/* req, res */) {
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { memberId } = req.params;
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ async function emailUnsubscribe (req, res) {
|
||||
notEmpty: { errorMessage: res.t('missingUnsubscriptionCode') },
|
||||
},
|
||||
});
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const data = JSON.parse(decrypt(req.query.code));
|
||||
|
||||
|
||||
@@ -110,8 +110,8 @@ async function registerLocal (req, res, { isV3 = false }) {
|
||||
},
|
||||
});
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const issues = verifyUsername(req.body.username, res);
|
||||
if (issues.length > 0) throw new BadRequest(issues.join(' '));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { param, query , validationResult } from 'express-validator';
|
||||
import {
|
||||
model as User,
|
||||
publicFields as memberFields,
|
||||
@@ -22,13 +23,13 @@ async function getMembersTasksForChallenge (members, challenge) {
|
||||
}
|
||||
|
||||
export async function handleGetMembersForChallenge (req, res) {
|
||||
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
||||
req.checkQuery('lastId').optional().notEmpty().isUUID();
|
||||
await param('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID().run(req);
|
||||
await query('lastId').optional().notEmpty().isUUID().run(req);
|
||||
// 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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const { challengeId } = req.params;
|
||||
const { lastId } = req.query;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import nconf from 'nconf';
|
||||
import moment from 'moment';
|
||||
import { param , validationResult } from 'express-validator';
|
||||
import { getAuthorEmailFromMessage } from '../chat';
|
||||
import ChatReporter from './chatReporter';
|
||||
import {
|
||||
@@ -23,11 +24,11 @@ export default class GroupChatReporter extends ChatReporter {
|
||||
}
|
||||
|
||||
async validate () {
|
||||
this.req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
this.req.checkParams('chatId', apiError('chatIdRequired')).notEmpty();
|
||||
await param('groupId', apiError('groupIdRequired')).notEmpty().run(req)
|
||||
await param('chatId', apiError('chatIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = this.req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = this.validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const group = await Group.getGroup({
|
||||
user: this.user,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { param , validationResult } from 'express-validator';
|
||||
import { model as User } from '../../models/user';
|
||||
|
||||
import ChatReporter from './chatReporter';
|
||||
@@ -20,10 +21,10 @@ export default class InboxChatReporter extends ChatReporter {
|
||||
}
|
||||
|
||||
async validate () {
|
||||
this.req.checkParams('messageId', apiError('messageIdRequired')).notEmpty();
|
||||
await param('messageId', apiError('messageIdRequired')).notEmpty().run(req)
|
||||
|
||||
const validationErrors = this.req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = this.validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
if (this.user.hasPermission('moderator') && this.req.query.userId) {
|
||||
this.inboxUser = await User.findOne({ _id: this.req.query.userId }).exec();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { model as User } from '../../models/user';
|
||||
import * as slack from '../slack';
|
||||
import { param , validationResult } from 'express-validator';
|
||||
|
||||
import ChatReporter from './chatReporter';
|
||||
import {
|
||||
@@ -15,10 +16,10 @@ export default class ProfileReporter extends ChatReporter {
|
||||
}
|
||||
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = this.validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
const flaggedUser = await User.findOne(
|
||||
{ _id: this.req.params.memberId },
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { param , validationResult } from 'express-validator';
|
||||
import { model as Coupon } from '../../models/coupon';
|
||||
|
||||
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();
|
||||
if (validationErrors) throw validationErrors;
|
||||
const validationErrors = validationResult(req).array();
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
await Coupon.apply(user, req, req.params.code);
|
||||
}
|
||||
|
||||
@@ -164,18 +164,24 @@ export default async function highlightMentions (text) {
|
||||
|
||||
if (mentions && mentions.length <= 5) {
|
||||
const usernames = mentions.map(mention => mention.substr(1));
|
||||
const usernameRegexes = usernames.map(username => new RegExp(`^${escapeRegExp(username)}$`, 'i'));
|
||||
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'])
|
||||
.lean()
|
||||
.exec();
|
||||
const baseUrl = determineBaseUrl();
|
||||
members.forEach(member => {
|
||||
const { username } = member.auth.local;
|
||||
const regex = new RegExp(`@${username}(?![\\-\\w])`, 'g');
|
||||
const replacement = `[@${username}](${baseUrl}/profile/${member._id})`;
|
||||
const regex = new RegExp(`@${escapeRegExp(username)}(?![\\-\\w])`, 'gi');
|
||||
|
||||
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 { chatModel as Chat } from '../models/message';
|
||||
import * as Tasks from '../models/task';
|
||||
@@ -157,9 +159,9 @@ async function castSpell (req, res, { isV3 = false }) {
|
||||
const quantity = req.body.quantity || 1;
|
||||
|
||||
// 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;
|
||||
|
||||
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
|
||||
// with a tasksOrder that doens't match reality
|
||||
const validationErrors = newTask.validateSync();
|
||||
if (validationErrors) throw validationErrors;
|
||||
if (validationErrors && validationErrors.length > 0) throw validationErrors;
|
||||
|
||||
// Otherwise update the user/challenge/group
|
||||
if (!taskOrderToAdd[`${taskType}s`]) taskOrderToAdd[`${taskType}s`] = [];
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import express from 'express';
|
||||
import expressValidator from 'express-validator';
|
||||
import path from 'path';
|
||||
import analytics from './analytics';
|
||||
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
|
||||
setupExpress(app);
|
||||
|
||||
app.use(expressValidator());
|
||||
app.use(analytics);
|
||||
app.use(setupBody);
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@ export default function errorHandler (err, req, res, next) { // eslint-disable-l
|
||||
}
|
||||
|
||||
// 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.errors = err.map(paramErr => ({
|
||||
message: paramErr.msg,
|
||||
param: paramErr.param,
|
||||
param: paramErr.path,
|
||||
value: paramErr.value,
|
||||
}));
|
||||
}
|
||||
@@ -66,7 +66,7 @@ export default function errorHandler (err, req, res, next) { // eslint-disable-l
|
||||
responseErr = new InternalServerError();
|
||||
}
|
||||
|
||||
if (!err.skipLogging) {
|
||||
if (!err.skipLogging && err instanceof Error) {
|
||||
// log the error
|
||||
logger.error(err, {
|
||||
method: req.method,
|
||||
|
||||
@@ -11,7 +11,7 @@ const router = express.Router(); // eslint-disable-line new-cap
|
||||
|
||||
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).`);
|
||||
return next(error);
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ const router = express.Router(); // eslint-disable-line new-cap
|
||||
|
||||
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).`);
|
||||
return next(error);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user