From 58651caa32532f04b5fd11f4105827ee4223aa3c Mon Sep 17 00:00:00 2001 From: Fabien O'Carroll Date: Wed, 9 Oct 2019 14:16:13 +0700 Subject: [PATCH] Removed members endpoint from admin v2 api no-issue --- core/server/api/v2/index.js | 4 - core/server/api/v2/members.js | 209 -------------- .../v2/utils/serializers/output/members.js | 77 ------ .../api/v2/utils/validators/input/members.js | 15 -- .../validators/input/schemas/members-add.json | 22 -- .../input/schemas/members-edit.json | 21 -- .../validators/input/schemas/members.json | 42 --- core/server/web/api/v2/admin/routes.js | 17 -- .../regression/api/v2/admin/members_spec.js | 255 ------------------ 9 files changed, 662 deletions(-) delete mode 100644 core/server/api/v2/members.js delete mode 100644 core/server/api/v2/utils/serializers/output/members.js delete mode 100644 core/server/api/v2/utils/validators/input/members.js delete mode 100644 core/server/api/v2/utils/validators/input/schemas/members-add.json delete mode 100644 core/server/api/v2/utils/validators/input/schemas/members-edit.json delete mode 100644 core/server/api/v2/utils/validators/input/schemas/members.json delete mode 100644 core/test/regression/api/v2/admin/members_spec.js diff --git a/core/server/api/v2/index.js b/core/server/api/v2/index.js index ce50008de0..2932b32461 100644 --- a/core/server/api/v2/index.js +++ b/core/server/api/v2/index.js @@ -71,10 +71,6 @@ module.exports = { return shared.pipeline(require('./subscribers'), localUtils); }, - get members() { - return shared.pipeline(require('./members'), localUtils); - }, - get images() { return shared.pipeline(require('./images'), localUtils); }, diff --git a/core/server/api/v2/members.js b/core/server/api/v2/members.js deleted file mode 100644 index bfd6231cc1..0000000000 --- a/core/server/api/v2/members.js +++ /dev/null @@ -1,209 +0,0 @@ -// NOTE: We must not cache references to membersService.api -// as it is a getter and may change during runtime. -const Promise = require('bluebird'); -const membersService = require('../../services/members'); -const common = require('../../lib/common'); -const fsLib = require('../../lib/fs'); - -const members = { - docName: 'members', - browse: { - options: [ - 'limit', - 'fields', - 'filter', - 'order', - 'debug', - 'page' - ], - permissions: true, - validation: {}, - query(frame) { - return membersService.api.members.list(frame.options); - } - }, - - read: { - headers: {}, - data: [ - 'id', - 'email' - ], - validation: {}, - permissions: true, - async query(frame) { - const member = await membersService.api.members.get(frame.data, frame.options); - if (!member) { - throw new common.errors.NotFoundError({ - message: common.i18n.t('errors.api.members.memberNotFound') - }); - } - return member; - } - }, - - add: { - statusCode: 201, - headers: {}, - options: [ - 'send_email', - 'email_type' - ], - validation: { - data: { - email: {required: true} - }, - options: { - email_type: { - values: ['signin', 'signup', 'subscribe'] - } - } - }, - permissions: true, - query(frame) { - // NOTE: Promise.resolve() is here for a reason! Method has to return an instance - // of a Bluebird promise to allow reflection. If decided to be replaced - // with something else, e.g: async/await, CSV export function - // would need a deep rewrite (see failing tests if this line is removed) - return Promise.resolve() - .then(() => { - return membersService.api.members.create(frame.data.members[0], { - sendEmail: frame.options.send_email, - emailType: frame.options.email_type - }); - }) - .then((member) => { - if (member) { - return Promise.resolve(member); - } - }) - .catch((error) => { - if (error.code && error.message.toLowerCase().indexOf('unique') !== -1) { - return Promise.reject(new common.errors.ValidationError({message: common.i18n.t('errors.api.members.memberAlreadyExists')})); - } - - return Promise.reject(error); - }); - } - }, - - edit: { - statusCode: 200, - headers: {}, - options: [ - 'id' - ], - validation: { - options: { - id: { - required: true - } - } - }, - permissions: true, - async query(frame) { - const member = await membersService.api.members.update(frame.data.members[0], frame.options); - return member; - } - }, - - destroy: { - statusCode: 204, - headers: {}, - options: [ - 'id' - ], - validation: { - options: { - id: { - required: true - } - } - }, - permissions: true, - async query(frame) { - frame.options.require = true; - await membersService.api.members.destroy(frame.options); - return null; - } - }, - - exportCSV: { - headers: { - disposition: { - type: 'csv', - value() { - const datetime = (new Date()).toJSON().substring(0, 10); - return `members.${datetime}.csv`; - } - } - }, - response: { - format: 'plain' - }, - permissions: { - method: 'browse' - }, - validation: {}, - query(frame) { - return membersService.api.members.list(frame.options); - } - }, - - importCSV: { - statusCode: 201, - permissions: { - method: 'add' - }, - async query(frame) { - let filePath = frame.file.path, - fulfilled = 0, - invalid = 0, - duplicates = 0; - - return fsLib.readCSV({ - path: filePath, - columnsToExtract: [{name: 'email', lookup: /email/i}, {name: 'name', lookup: /name/i}] - }).then((result) => { - return Promise.all(result.map((entry) => { - const api = require('./index'); - - return api.members.add.query({ - data: { - members: [{ - email: entry.email, - name: entry.name - }] - }, - options: { - context: frame.options.context, - options: {send_email: false} - } - }).reflect(); - })).each((inspection) => { - if (inspection.isFulfilled()) { - fulfilled = fulfilled + 1; - } else { - if (inspection.reason() instanceof common.errors.ValidationError) { - duplicates = duplicates + 1; - } else { - invalid = invalid + 1; - } - } - }); - }).then(() => { - return { - meta: { - stats: { - imported: fulfilled, - duplicates: duplicates, - invalid: invalid - } - } - }; - }); - } - } -}; - -module.exports = members; diff --git a/core/server/api/v2/utils/serializers/output/members.js b/core/server/api/v2/utils/serializers/output/members.js deleted file mode 100644 index 4eeca69f5e..0000000000 --- a/core/server/api/v2/utils/serializers/output/members.js +++ /dev/null @@ -1,77 +0,0 @@ -const common = require('../../../../../lib/common'); -const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:members'); - -module.exports = { - browse(data, apiConfig, frame) { - debug('browse'); - - frame.response = data; - }, - - add(data, apiConfig, frame) { - debug('add'); - - frame.response = { - members: [data] - }; - }, - - edit(data, apiConfig, frame) { - debug('edit'); - - frame.response = { - members: [data] - }; - }, - - read(data, apiConfig, frame) { - debug('read'); - - if (!data) { - return Promise.reject(new common.errors.NotFoundError({ - message: common.i18n.t('errors.api.members.memberNotFound') - })); - } - - frame.response = { - members: [data] - }; - }, - - exportCSV(models, apiConfig, frame) { - debug('exportCSV'); - - const fields = ['id', 'email', 'name', 'created_at', 'deleted_at']; - - function formatCSV(data) { - let csv = `${fields.join(',')}\r\n`, - entry, - field, - j, - i; - - for (j = 0; j < data.length; j = j + 1) { - entry = data[j]; - - for (i = 0; i < fields.length; i = i + 1) { - field = fields[i]; - csv += entry[field] !== null ? entry[field] : ''; - if (i !== fields.length - 1) { - csv += ','; - } - } - csv += '\r\n'; - } - - return csv; - } - - frame.response = formatCSV(models.members); - }, - - importCSV(data, apiConfig, frame) { - debug('importCSV'); - - frame.response = data; - } -}; diff --git a/core/server/api/v2/utils/validators/input/members.js b/core/server/api/v2/utils/validators/input/members.js deleted file mode 100644 index aa01b06aeb..0000000000 --- a/core/server/api/v2/utils/validators/input/members.js +++ /dev/null @@ -1,15 +0,0 @@ -const jsonSchema = require('../utils/json-schema'); - -module.exports = { - add(apiConfig, frame) { - const schema = require('./schemas/members-add'); - const definitions = require('./schemas/members'); - return jsonSchema.validate(schema, definitions, frame.data); - }, - - edit(apiConfig, frame) { - const schema = require('./schemas/members-edit'); - const definitions = require('./schemas/members'); - return jsonSchema.validate(schema, definitions, frame.data); - } -}; diff --git a/core/server/api/v2/utils/validators/input/schemas/members-add.json b/core/server/api/v2/utils/validators/input/schemas/members-add.json deleted file mode 100644 index d6370b6595..0000000000 --- a/core/server/api/v2/utils/validators/input/schemas/members-add.json +++ /dev/null @@ -1,22 +0,0 @@ - -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "members.add", - "title": "members.add", - "description": "Schema for members.add", - "type": "object", - "additionalProperties": false, - "properties": { - "members": { - "type": "array", - "minItems": 1, - "maxItems": 1, - "items": { - "type": "object", - "allOf": [{"$ref": "members#/definitions/member"}], - "required": ["email"] - } - } - }, - "required": ["members"] - } diff --git a/core/server/api/v2/utils/validators/input/schemas/members-edit.json b/core/server/api/v2/utils/validators/input/schemas/members-edit.json deleted file mode 100644 index a1a9adf791..0000000000 --- a/core/server/api/v2/utils/validators/input/schemas/members-edit.json +++ /dev/null @@ -1,21 +0,0 @@ - -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "members.edit", - "title": "members.edit", - "description": "Schema for members.edit", - "type": "object", - "additionalProperties": false, - "properties": { - "members": { - "type": "array", - "minItems": 1, - "maxItems": 1, - "items": { - "type": "object", - "allOf": [{"$ref": "members#/definitions/member"}] - } - } - }, - "required": ["members"] - } diff --git a/core/server/api/v2/utils/validators/input/schemas/members.json b/core/server/api/v2/utils/validators/input/schemas/members.json deleted file mode 100644 index 1a2dc9b7ac..0000000000 --- a/core/server/api/v2/utils/validators/input/schemas/members.json +++ /dev/null @@ -1,42 +0,0 @@ - -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "members", - "title": "members", - "description": "Base members definitions", - "definitions": { - "member": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "minLength": 1, - "maxLength": 191, - "pattern": "^([^,]|$)" - }, - "email": { - "type": "string", - "minLength": 1, - "maxLength": 191, - "pattern": "^([^,]|$)" - }, - "id": { - "strip": true - }, - "created_at": { - "strip": true - }, - "created_by": { - "strip": true - }, - "updated_at": { - "strip": true - }, - "updated_by": { - "strip": true - } - } - } - } -} diff --git a/core/server/web/api/v2/admin/routes.js b/core/server/web/api/v2/admin/routes.js index a17e3bdc8e..b99a42af25 100644 --- a/core/server/web/api/v2/admin/routes.js +++ b/core/server/web/api/v2/admin/routes.js @@ -101,23 +101,6 @@ module.exports = function apiRoutes() { router.del('/subscribers/:id', shared.middlewares.labs.subscribers, mw.authAdminApi, http(apiv2.subscribers.destroy)); router.del('/subscribers/email/:email', shared.middlewares.labs.subscribers, mw.authAdminApi, http(apiv2.subscribers.destroy)); - // ## Members - router.get('/members', shared.middlewares.labs.members, mw.authAdminApi, http(apiv2.members.browse)); - router.post('/members', shared.middlewares.labs.members, mw.authAdminApi, http(apiv2.members.add)); - - router.get('/members/csv', shared.middlewares.labs.members, mw.authAdminApi, http(apiv2.members.exportCSV)); - router.post('/members/csv', - shared.middlewares.labs.members, - mw.authAdminApi, - upload.single('membersfile'), - shared.middlewares.validation.upload({type: 'members'}), - http(apiv2.members.importCSV) - ); - - router.get('/members/:id', shared.middlewares.labs.members, mw.authAdminApi, http(apiv2.members.read)); - router.put('/members/:id', shared.middlewares.labs.members, mw.authAdminApi, http(apiv2.members.edit)); - router.del('/members/:id', shared.middlewares.labs.members, mw.authAdminApi, http(apiv2.members.destroy)); - // ## Roles router.get('/roles/', mw.authAdminApi, http(apiv2.roles.browse)); diff --git a/core/test/regression/api/v2/admin/members_spec.js b/core/test/regression/api/v2/admin/members_spec.js deleted file mode 100644 index 779f2ac1f2..0000000000 --- a/core/test/regression/api/v2/admin/members_spec.js +++ /dev/null @@ -1,255 +0,0 @@ -const path = require('path'); -const should = require('should'); -const supertest = require('supertest'); -const sinon = require('sinon'); -const testUtils = require('../../../../utils'); -const localUtils = require('./utils'); -const config = require('../../../../../server/config'); -const labs = require('../../../../../server/services/labs'); - -const ghost = testUtils.startGhost; - -let request; - -describe('Members API', function () { - before(function () { - sinon.stub(labs, 'isSet').withArgs('members').returns(true); - }); - - after(function () { - sinon.restore(); - }); - - before(function () { - return ghost() - .then(function () { - request = supertest.agent(config.get('url')); - }) - .then(function () { - return localUtils.doAuth(request, 'member'); - }); - }); - - it('Can browse', function () { - return request - .get(localUtils.API.getApiQuery('members/')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - localUtils.API.checkResponse(jsonResponse.members[0], 'member'); - - testUtils.API.isISO8601(jsonResponse.members[0].created_at).should.be.true(); - jsonResponse.members[0].created_at.should.be.an.instanceof(String); - - jsonResponse.meta.pagination.should.have.property('page', 1); - jsonResponse.meta.pagination.should.have.property('limit', 15); - jsonResponse.meta.pagination.should.have.property('pages', 1); - jsonResponse.meta.pagination.should.have.property('total', 1); - jsonResponse.meta.pagination.should.have.property('next', null); - jsonResponse.meta.pagination.should.have.property('prev', null); - }); - }); - - it('Can read', function () { - return request - .get(localUtils.API.getApiQuery(`members/${testUtils.DataGenerator.Content.members[0].id}/`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'stripe'); - }); - }); - - it('Can add', function () { - const member = { - name: 'test', - email: 'memberTestAdd@test.com' - }; - - return request - .post(localUtils.API.getApiQuery(`members/`)) - .send({members: [member]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(201) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - - jsonResponse.members[0].name.should.equal(member.name); - jsonResponse.members[0].email.should.equal(member.email); - }) - .then(() => { - return request - .post(localUtils.API.getApiQuery(`members/`)) - .send({members: [member]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(422); - }); - }); - - it('Should fail when passing incorrect email_type query parameter', function () { - const member = { - name: 'test', - email: 'memberTestAdd@test.com' - }; - - return request - .post(localUtils.API.getApiQuery(`members/?send_email=true&email_type=lel`)) - .send({members: [member]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(422); - }); - - it('Can edit by id', function () { - const memberToChange = { - name: 'change me', - email: 'member2Change@test.com' - }; - - const memberChanged = { - name: 'changed', - email: 'cantChangeMe@test.com' - }; - - return request - .post(localUtils.API.getApiQuery(`members/`)) - .send({members: [memberToChange]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(201) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - - return jsonResponse.members[0]; - }) - .then((newMember) => { - return request - .put(localUtils.API.getApiQuery(`members/${newMember.id}/`)) - .send({members: [memberChanged]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - - const jsonResponse = res.body; - - should.exist(jsonResponse); - should.exist(jsonResponse.members); - jsonResponse.members.should.have.length(1); - localUtils.API.checkResponse(jsonResponse.members[0], 'member'); - jsonResponse.members[0].name.should.equal(memberChanged.name); - jsonResponse.members[0].email.should.not.equal(memberChanged.email); - jsonResponse.members[0].email.should.equal(memberToChange.email); - }); - }); - }); - - it('Can destroy', function () { - const member = { - name: 'test', - email: 'memberTestDestroy@test.com' - }; - - return request - .post(localUtils.API.getApiQuery(`members/`)) - .send({members: [member]}) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(201) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - - const jsonResponse = res.body; - - should.exist(jsonResponse); - should.exist(jsonResponse.members); - - return jsonResponse.members[0]; - }) - .then((newMember) => { - return request - .delete(localUtils.API.getApiQuery(`members/${newMember.id}`)) - .set('Origin', config.get('url')) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(204) - .then(() => newMember); - }) - .then((newMember) => { - return request - .get(localUtils.API.getApiQuery(`members/${newMember.id}/`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404); - }); - }); - - it('Can export CSV', function () { - return request - .get(localUtils.API.getApiQuery(`members/csv/`)) - .set('Origin', config.get('url')) - .expect('Content-Type', /text\/csv/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - res.headers['content-disposition'].should.match(/Attachment;\sfilename="members/); - res.text.should.match(/id,email,name,created_at,deleted_at/); - res.text.should.match(/member1@test.com/); - res.text.should.match(/Mr Egg/); - }); - }); - - it('Can import CSV', function () { - return request - .post(localUtils.API.getApiQuery(`members/csv/`)) - .attach('membersfile', path.join(__dirname, '/../../../../utils/fixtures/csv/valid-members-import.csv')) - .set('Origin', config.get('url')) - .expect('Content-Type', /json/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(201) - .then((res) => { - should.not.exist(res.headers['x-cache-invalidate']); - const jsonResponse = res.body; - - should.exist(jsonResponse); - should.exist(jsonResponse.meta); - should.exist(jsonResponse.meta.stats); - - jsonResponse.meta.stats.imported.should.equal(2); - jsonResponse.meta.stats.duplicates.should.equal(0); - jsonResponse.meta.stats.invalid.should.equal(0); - }); - }); -});