mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
v3: port coupons
This commit is contained in:
@@ -142,5 +142,11 @@
|
|||||||
"cardTypeRequired": "Card type required",
|
"cardTypeRequired": "Card type required",
|
||||||
"cardTypeNotAllowed": "Unkown card type.",
|
"cardTypeNotAllowed": "Unkown card type.",
|
||||||
"mysteryItemIsEmpty": "Mystery items are empty",
|
"mysteryItemIsEmpty": "Mystery items are empty",
|
||||||
"mysteryItemOpened": "Mystery item opened."
|
"mysteryItemOpened": "Mystery item opened.",
|
||||||
|
"invalidCoupon": "Invalid coupon code.",
|
||||||
|
"couponUsed": "Coupon code already used.",
|
||||||
|
"noSudoAccess": "You don't have sudo access.",
|
||||||
|
"couponCodeRequired": "The coupon code is required.",
|
||||||
|
"eventRequired": "\"req.params.event\" is required.",
|
||||||
|
"countRequired": "\"req.query.count\" is required."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,7 @@ import eslint from 'gulp-eslint';
|
|||||||
|
|
||||||
const SERVER_FILES = [
|
const SERVER_FILES = [
|
||||||
'./website/src/**/api-v3/**/*.js',
|
'./website/src/**/api-v3/**/*.js',
|
||||||
'./website/src/models/user.js',
|
'./website/src/models/**',
|
||||||
'./website/src/models/task.js',
|
|
||||||
'./website/src/models/group.js',
|
|
||||||
'./website/src/models/challenge.js',
|
|
||||||
'./website/src/models/tag.js',
|
|
||||||
'./website/src/models/emailUnsubscription.js',
|
|
||||||
'./website/src/server.js',
|
'./website/src/server.js',
|
||||||
];
|
];
|
||||||
const COMMON_FILES = [
|
const COMMON_FILES = [
|
||||||
|
|||||||
39
test/api/v3/integration/coupons/GET-coupons.test.js
Normal file
39
test/api/v3/integration/coupons/GET-coupons.test.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
resetHabiticaDB,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
|
||||||
|
describe('GET /coupons/', () => {
|
||||||
|
let user;
|
||||||
|
before(async () => {
|
||||||
|
await resetHabiticaDB();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if user has no sudo permission', async () => {
|
||||||
|
await user.get('/user'); // needed so the request after this will authenticate with the correct cookie session
|
||||||
|
await expect(user.get(`/coupons`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('noSudoAccess'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the coupons in CSV format ordered by creation date', async () => {
|
||||||
|
await user.update({
|
||||||
|
'contributor.sudo': true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let coupons = await user.post('/coupons/generate/wondercon?count=11');
|
||||||
|
let res = await user.get(`/coupons`);
|
||||||
|
let splitRes = res.split('\n');
|
||||||
|
|
||||||
|
expect(splitRes.length).to.equal(13);
|
||||||
|
expect(splitRes[0]).to.equal('code,event,date,user');
|
||||||
|
expect(splitRes[6].split(',')[1]).to.equal(coupons[5].event);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
resetHabiticaDB,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
|
||||||
|
describe('POST /coupons/enter/:code', () => {
|
||||||
|
let user;
|
||||||
|
let sudoUser;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await resetHabiticaDB();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
sudoUser = await generateUser({
|
||||||
|
'contributor.sudo': true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if code is missing', async () => {
|
||||||
|
await expect(user.post(`/coupons/enter`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: 'Not found.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if code is invalid', async () => {
|
||||||
|
await expect(user.post(`/coupons/enter/notValid`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('invalidCoupon'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if coupon has been used', async () => {
|
||||||
|
let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
|
||||||
|
await user.post(`/coupons/enter/${coupon._id}`); // use coupon
|
||||||
|
|
||||||
|
await expect(user.post(`/coupons/enter/${coupon._id}`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('couponUsed'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply the coupon to the user', async () => {
|
||||||
|
let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
|
||||||
|
let userRes = await user.post(`/coupons/enter/${coupon._id}`);
|
||||||
|
expect(userRes._id).to.equal(user._id);
|
||||||
|
expect(userRes.items.gear.owned.eyewear_special_wondercon_red).to.be.true;
|
||||||
|
expect(userRes.items.gear.owned.eyewear_special_wondercon_black).to.be.true;
|
||||||
|
expect(userRes.items.gear.owned.back_special_wondercon_black).to.be.true;
|
||||||
|
expect(userRes.items.gear.owned.back_special_wondercon_red).to.be.true;
|
||||||
|
expect(userRes.items.gear.owned.body_special_wondercon_red).to.be.true;
|
||||||
|
expect(userRes.items.gear.owned.body_special_wondercon_black).to.be.true;
|
||||||
|
expect(userRes.items.gear.owned.body_special_wondercon_gold).to.be.true;
|
||||||
|
expect(userRes.extra).to.eql({signupEvent: 'wondercon'});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
resetHabiticaDB,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
import couponCode from 'coupon-code';
|
||||||
|
|
||||||
|
describe('POST /coupons/generate/:event', () => {
|
||||||
|
let user;
|
||||||
|
before(async () => {
|
||||||
|
await resetHabiticaDB();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser({
|
||||||
|
'contributor.sudo': true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if user has no sudo permission', async () => {
|
||||||
|
await user.update({
|
||||||
|
'contributor.sudo': false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(user.post(`/coupons/generate/aaa`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('noSudoAccess'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if event is missing', async () => {
|
||||||
|
await expect(user.post(`/coupons/generate`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: 'Not found.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if event is invalid', async () => {
|
||||||
|
await expect(user.post(`/coupons/generate/notValid?count=1`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: 'Coupon validation failed',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if count is missing', async () => {
|
||||||
|
await expect(user.post(`/coupons/generate/notValid`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('invalidReqParams'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate coupons', async () => {
|
||||||
|
await user.update({
|
||||||
|
'contributor.sudo': true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let coupons = await user.post('/coupons/generate/wondercon?count=2');
|
||||||
|
expect(coupons.length).to.equal(2);
|
||||||
|
expect(coupons[0].event).to.equal('wondercon');
|
||||||
|
expect(couponCode.validate(coupons[1]._id)).to.not.equal(''); // '' means invalid
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
requester,
|
||||||
|
resetHabiticaDB,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
|
||||||
|
describe('POST /coupons/validate/:code', () => {
|
||||||
|
let api = requester();
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await resetHabiticaDB();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if code is missing', async () => {
|
||||||
|
await expect(api.post(`/coupons/validate`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: 'Not found.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if coupon code is valid', async () => {
|
||||||
|
let sudoUser = await generateUser({
|
||||||
|
'contributor.sudo': true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
|
||||||
|
let res = await api.post(`/coupons/validate/${coupon._id}`);
|
||||||
|
expect(res).to.eql({valid: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if coupon code is valid', async () => {
|
||||||
|
let res = await api.post(`/coupons/validate/notValid`);
|
||||||
|
expect(res).to.eql({valid: false});
|
||||||
|
});
|
||||||
|
});
|
||||||
57
test/api/v3/unit/middlewares/ensureAccessRight.test.js
Normal file
57
test/api/v3/unit/middlewares/ensureAccessRight.test.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/* eslint-disable global-require */
|
||||||
|
import {
|
||||||
|
generateRes,
|
||||||
|
generateReq,
|
||||||
|
generateNext,
|
||||||
|
} from '../../../../helpers/api-unit.helper';
|
||||||
|
import i18n from '../../../../../common/script/i18n';
|
||||||
|
import { ensureAdmin, ensureSudo } from '../../../../../website/src/middlewares/api-v3/ensureAccessRight';
|
||||||
|
import { NotAuthorized } from '../../../../../website/src/libs/api-v3/errors';
|
||||||
|
|
||||||
|
describe('ensure access middlewares', () => {
|
||||||
|
let res, req, next;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
res = generateRes();
|
||||||
|
req = generateReq();
|
||||||
|
next = generateNext();
|
||||||
|
});
|
||||||
|
|
||||||
|
context('ensure admin', () => {
|
||||||
|
it('returns not authorized when user is not an admin', () => {
|
||||||
|
res.locals = {user: {contributor: {admin: false}}};
|
||||||
|
|
||||||
|
ensureAdmin(req, res, next);
|
||||||
|
|
||||||
|
expect(next).to.be.calledWith(new NotAuthorized(i18n.t('noAdminAccess')));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes when user is an admin', () => {
|
||||||
|
res.locals = {user: {contributor: {admin: true}}};
|
||||||
|
|
||||||
|
ensureAdmin(req, res, next);
|
||||||
|
|
||||||
|
expect(next).to.be.calledOnce;
|
||||||
|
expect(next.args[0]).to.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('ensure sudo', () => {
|
||||||
|
it('returns not authorized when user is not a sudo user', () => {
|
||||||
|
res.locals = {user: {contributor: {sudo: false}}};
|
||||||
|
|
||||||
|
ensureSudo(req, res, next);
|
||||||
|
|
||||||
|
expect(next).to.be.calledWith(new NotAuthorized(i18n.t('noSudoAccess')));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes when user is a sudo user', () => {
|
||||||
|
res.locals = {user: {contributor: {sudo: true}}};
|
||||||
|
|
||||||
|
ensureSudo(req, res, next);
|
||||||
|
|
||||||
|
expect(next).to.be.calledOnce;
|
||||||
|
expect(next.args[0]).to.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,6 +5,7 @@ import { model as User } from '../../website/src/models/user';
|
|||||||
import { model as Group } from '../../website/src/models/group';
|
import { model as Group } from '../../website/src/models/group';
|
||||||
import mongo from './mongo'; // eslint-disable-line
|
import mongo from './mongo'; // eslint-disable-line
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import i18n from '../../common/script/i18n';
|
||||||
|
|
||||||
afterEach((done) => {
|
afterEach((done) => {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
@@ -32,6 +33,9 @@ export function generateRes (options = {}) {
|
|||||||
group: generateGroup(options.localsGroup),
|
group: generateGroup(options.localsGroup),
|
||||||
},
|
},
|
||||||
set: sandbox.stub(),
|
set: sandbox.stub(),
|
||||||
|
t (string) {
|
||||||
|
return i18n.t(string);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return defaults(options, defaultRes);
|
return defaults(options, defaultRes);
|
||||||
|
|||||||
125
website/src/controllers/api-v3/coupon.js
Normal file
125
website/src/controllers/api-v3/coupon.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import csvStringify from '../../libs/api-v3/csvStringify';
|
||||||
|
import {
|
||||||
|
authWithHeaders,
|
||||||
|
authWithSession,
|
||||||
|
} from '../../middlewares/api-v3/auth';
|
||||||
|
import cron from '../../middlewares/api-v3/cron';
|
||||||
|
import { ensureSudo } from '../../middlewares/api-v3/ensureAccessRight';
|
||||||
|
import { model as Coupon } from '../../models/coupon';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import couponCode from 'coupon-code';
|
||||||
|
|
||||||
|
let api = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /coupons Get coupons (sudo users only)
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName GetCoupons
|
||||||
|
* @apiGroup Coupon
|
||||||
|
*
|
||||||
|
* @apiSuccess string Coupons in CSV format
|
||||||
|
*/
|
||||||
|
api.getCoupons = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/coupons',
|
||||||
|
middlewares: [authWithSession, cron, ensureSudo],
|
||||||
|
async handler (req, res) {
|
||||||
|
let coupons = await Coupon.find().sort('createdAt').lean().exec();
|
||||||
|
|
||||||
|
let output = [['code', 'event', 'date', 'user']].concat(_.map(coupons, coupon => {
|
||||||
|
return [coupon._id, coupon.event, coupon.createdAt, coupon.user];
|
||||||
|
}));
|
||||||
|
let csv = await csvStringify(output);
|
||||||
|
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'text/csv',
|
||||||
|
'Content-disposition': `attachment; filename=habitica-coupons.csv`,
|
||||||
|
});
|
||||||
|
res.status(200).send(csv);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /coupons/generate/:event Generate coupons for an event (sudo users only)
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName GenerateCoupons
|
||||||
|
* @apiGroup Coupon
|
||||||
|
*
|
||||||
|
* @apiParam {string} event The event for which the coupon should be generated
|
||||||
|
* @apiParam {number} count Query parameter to specify the number of coupon codes to generate
|
||||||
|
*
|
||||||
|
* @apiSuccess array Generated coupons
|
||||||
|
*/
|
||||||
|
api.generateCoupons = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/coupons/generate/:event',
|
||||||
|
middlewares: [authWithHeaders(), cron, ensureSudo],
|
||||||
|
async handler (req, res) {
|
||||||
|
req.checkParams('event', res.t('eventRequired')).notEmpty();
|
||||||
|
req.checkQuery('count', res.t('countRequired')).notEmpty().isNumeric();
|
||||||
|
|
||||||
|
let validationErrors = req.validationErrors();
|
||||||
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
|
let coupons = await Coupon.generate(req.params.event, req.query.count);
|
||||||
|
res.respond(200, coupons);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /user/coupon/:code Enter coupon code
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName EnterCouponCode
|
||||||
|
* @apiGroup Coupon
|
||||||
|
*
|
||||||
|
* @apiParam {string} code The coupon code to apply
|
||||||
|
*
|
||||||
|
* @apiSuccess object User object
|
||||||
|
*/
|
||||||
|
api.enterCouponCode = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/coupons/enter/:code',
|
||||||
|
middlewares: [authWithHeaders(), cron],
|
||||||
|
async handler (req, res) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
|
||||||
|
req.checkParams('code', res.t('couponCodeRequired')).notEmpty();
|
||||||
|
|
||||||
|
let validationErrors = req.validationErrors();
|
||||||
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
|
await Coupon.apply(user, req, req.params.code);
|
||||||
|
res.respond(200, user);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /coupons/validate/:code Validate a coupon code
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName ValidateCoupon
|
||||||
|
* @apiGroup Coupon
|
||||||
|
*
|
||||||
|
* @apiSuccess valid {boolean} true or false
|
||||||
|
*/
|
||||||
|
api.validateCoupon = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/coupons/validate/:code',
|
||||||
|
middlewares: [authWithHeaders(true)],
|
||||||
|
async handler (req, res) {
|
||||||
|
req.checkParams('code', res.t('couponCodeRequired')).notEmpty();
|
||||||
|
|
||||||
|
let validationErrors = req.validationErrors();
|
||||||
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
|
let valid = false;
|
||||||
|
let code = couponCode.validate(req.params.code);
|
||||||
|
if (code) {
|
||||||
|
let coupon = await Coupon.findOne({_id: code}).exec();
|
||||||
|
valid = coupon ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.respond(200, {valid});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = api;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { authWithHeaders } from '../../middlewares/api-v3/auth';
|
import { authWithHeaders } from '../../middlewares/api-v3/auth';
|
||||||
|
import { ensureAdmin } from '../../middlewares/api-v3/ensureAccessRight';
|
||||||
import cron from '../../middlewares/api-v3/cron';
|
import cron from '../../middlewares/api-v3/cron';
|
||||||
import { model as User } from '../../models/user';
|
import { model as User } from '../../models/user';
|
||||||
import {
|
import {
|
||||||
NotFound,
|
NotFound,
|
||||||
NotAuthorized,
|
|
||||||
} from '../../libs/api-v3/errors';
|
} from '../../libs/api-v3/errors';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
@@ -90,9 +90,8 @@ const heroAdminFields = 'contributor balance profile.name purchased items auth';
|
|||||||
api.getHero = {
|
api.getHero = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/hall/heroes/:heroId',
|
url: '/hall/heroes/:heroId',
|
||||||
middlewares: [authWithHeaders(), cron],
|
middlewares: [authWithHeaders(), cron, ensureAdmin],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
|
||||||
let heroId = req.params.heroId;
|
let heroId = req.params.heroId;
|
||||||
|
|
||||||
req.checkParams('heroId', res.t('heroIdRequired')).notEmpty().isUUID();
|
req.checkParams('heroId', res.t('heroIdRequired')).notEmpty().isUUID();
|
||||||
@@ -100,10 +99,6 @@ api.getHero = {
|
|||||||
let validationErrors = req.validationErrors();
|
let validationErrors = req.validationErrors();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
if (!user.contributor.admin) {
|
|
||||||
throw new NotAuthorized(res.t('noAdminAccess'));
|
|
||||||
}
|
|
||||||
|
|
||||||
let hero = await User
|
let hero = await User
|
||||||
.findById(heroId)
|
.findById(heroId)
|
||||||
.select(heroAdminFields)
|
.select(heroAdminFields)
|
||||||
@@ -132,9 +127,8 @@ const gemsPerTier = {1: 3, 2: 3, 3: 3, 4: 4, 5: 4, 6: 4, 7: 4, 8: 0, 9: 0};
|
|||||||
api.updateHero = {
|
api.updateHero = {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
url: '/hall/heroes/:heroId',
|
url: '/hall/heroes/:heroId',
|
||||||
middlewares: [authWithHeaders(), cron],
|
middlewares: [authWithHeaders(), cron, ensureAdmin],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
|
||||||
let heroId = req.params.heroId;
|
let heroId = req.params.heroId;
|
||||||
let updateData = req.body;
|
let updateData = req.body;
|
||||||
|
|
||||||
@@ -143,10 +137,6 @@ api.updateHero = {
|
|||||||
let validationErrors = req.validationErrors();
|
let validationErrors = req.validationErrors();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
if (!user.contributor.admin) {
|
|
||||||
throw new NotAuthorized(res.t('noAdminAccess'));
|
|
||||||
}
|
|
||||||
|
|
||||||
let hero = await User.findById(heroId).exec();
|
let hero = await User.findById(heroId).exec();
|
||||||
if (!hero) throw new NotFound(res.t('userWithIDNotFound', {userId: heroId}));
|
if (!hero) throw new NotFound(res.t('userWithIDNotFound', {userId: heroId}));
|
||||||
|
|
||||||
|
|||||||
23
website/src/middlewares/api-v3/ensureAccessRight.js
Normal file
23
website/src/middlewares/api-v3/ensureAccessRight.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import {
|
||||||
|
NotAuthorized,
|
||||||
|
} from '../../libs/api-v3/errors';
|
||||||
|
|
||||||
|
export function ensureAdmin (req, res, next) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
|
||||||
|
if (!user.contributor.admin) {
|
||||||
|
return next(new NotAuthorized(res.t('noAdminAccess')));
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureSudo (req, res, next) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
|
||||||
|
if (!user.contributor.sudo) {
|
||||||
|
return next(new NotAuthorized(res.t('noSudoAccess')));
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
@@ -1,59 +1,58 @@
|
|||||||
var mongoose = require("mongoose");
|
/* eslint-disable camelcase */
|
||||||
var shared = require('../../../common');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var async = require('async');
|
|
||||||
var cc = require('coupon-code');
|
|
||||||
var autoinc = require('mongoose-id-autoinc');
|
|
||||||
|
|
||||||
var CouponSchema = new mongoose.Schema({
|
import mongoose from 'mongoose';
|
||||||
_id: {type: String, 'default': cc.generate},
|
import _ from 'lodash';
|
||||||
event: {type:String, enum:['wondercon','google_6mo']},
|
import shared from '../../../common';
|
||||||
user: {type: 'String', ref: 'User'}
|
import couponCode from 'coupon-code';
|
||||||
|
import baseModel from '../libs/api-v3/baseModel';
|
||||||
|
import {
|
||||||
|
BadRequest,
|
||||||
|
NotAuthorized,
|
||||||
|
} from '../libs/api-v3/errors';
|
||||||
|
|
||||||
|
export let schema = new mongoose.Schema({
|
||||||
|
event: {type: String, enum: ['wondercon', 'google_6mo']},
|
||||||
|
user: {type: String, ref: 'User'},
|
||||||
});
|
});
|
||||||
|
|
||||||
CouponSchema.statics.generate = function(event, count, callback) {
|
schema.plugin(baseModel, {
|
||||||
async.times(count, function(n,cb){
|
timestamps: true,
|
||||||
mongoose.model('Coupon').create({event: event}, cb);
|
|
||||||
}, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
CouponSchema.statics.apply = function(user, code, next){
|
|
||||||
async.auto({
|
|
||||||
get_coupon: function (cb) {
|
|
||||||
mongoose.model('Coupon').findById(cc.validate(code), cb);
|
|
||||||
},
|
|
||||||
apply_coupon: ['get_coupon', function (cb, results) {
|
|
||||||
if (!results.get_coupon) return cb("Invalid coupon code");
|
|
||||||
if (results.get_coupon.user) return cb("Coupon already used");
|
|
||||||
switch (results.get_coupon.event) {
|
|
||||||
case 'wondercon':
|
|
||||||
user.items.gear.owned.eyewear_special_wondercon_red = true;
|
|
||||||
user.items.gear.owned.eyewear_special_wondercon_black = true;
|
|
||||||
user.items.gear.owned.back_special_wondercon_black = true;
|
|
||||||
user.items.gear.owned.back_special_wondercon_red = true;
|
|
||||||
user.items.gear.owned.body_special_wondercon_red = true;
|
|
||||||
user.items.gear.owned.body_special_wondercon_black = true;
|
|
||||||
user.items.gear.owned.body_special_wondercon_gold = true;
|
|
||||||
user.extra = {signupEvent: 'wondercon'};
|
|
||||||
user.save(cb);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
expire_coupon: ['apply_coupon', function (cb, results) {
|
|
||||||
results.get_coupon.user = user._id;
|
|
||||||
results.get_coupon.save(cb);
|
|
||||||
}]
|
|
||||||
}, function(err, results){
|
|
||||||
if (err) return next(err);
|
|
||||||
next(null,results.apply_coupon[0]);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
CouponSchema.plugin(autoinc.plugin, {
|
|
||||||
model: 'Coupon',
|
|
||||||
field: 'seq'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.schema = CouponSchema;
|
// Add _id field after plugin to override default _id format
|
||||||
module.exports.model = mongoose.model("Coupon", CouponSchema);
|
schema.add({
|
||||||
|
_id: {type: String, default: couponCode.generate},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
schema.statics.generate = async function generateCoupons (event, count = 1) {
|
||||||
|
let coupons = _.times(count, () => {
|
||||||
|
return {event};
|
||||||
|
});
|
||||||
|
|
||||||
|
return await this.create(coupons);
|
||||||
|
};
|
||||||
|
|
||||||
|
schema.statics.apply = async function applyCoupon (user, req, code) {
|
||||||
|
let coupon = await this.findById(couponCode.validate(code)).exec();
|
||||||
|
if (!coupon) throw new BadRequest(shared.i18n.t('invalidCoupon', req.language));
|
||||||
|
if (coupon.user) throw new NotAuthorized(shared.i18n.t('couponUsed', req.language));
|
||||||
|
|
||||||
|
if (coupon.event === 'wondercon') {
|
||||||
|
user.items.gear.owned.eyewear_special_wondercon_red = true;
|
||||||
|
user.items.gear.owned.eyewear_special_wondercon_black = true;
|
||||||
|
user.items.gear.owned.back_special_wondercon_black = true;
|
||||||
|
user.items.gear.owned.back_special_wondercon_red = true;
|
||||||
|
user.items.gear.owned.body_special_wondercon_red = true;
|
||||||
|
user.items.gear.owned.body_special_wondercon_black = true;
|
||||||
|
user.items.gear.owned.body_special_wondercon_gold = true;
|
||||||
|
user.extra = {signupEvent: 'wondercon'};
|
||||||
|
}
|
||||||
|
|
||||||
|
await user.save();
|
||||||
|
coupon.user = user._id;
|
||||||
|
await coupon.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.schema = schema;
|
||||||
|
export let model = mongoose.model('Coupon', schema);
|
||||||
|
|||||||
Reference in New Issue
Block a user