2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

Added Admin API for deleting members (#10673)

no issue

- Added new API to delete members
- Added methods to handle e2e member deletion
- Deleting member via Admin leads to
  - Removal of member from payment processor and cancelling all active subscriptions immediately
  - Removal of member information from DB
This commit is contained in:
Rishabh Garg 2019-04-13 10:38:56 +05:30 committed by GitHub
parent a7385f5e10
commit c03ca79c66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 110 additions and 1 deletions

View file

@ -29,6 +29,26 @@ const members = {
query(frame) {
return memberUserObject.get(frame.data, frame.options);
}
},
destroy: {
statusCode: 204,
headers: {},
options: [
'id'
],
validation: {
options: {
id: {
required: true
}
}
},
permissions: true,
query(frame) {
frame.options.require = true;
return memberUserObject.destroy(frame.options).return(null);
}
}
};

View file

@ -22,6 +22,7 @@ module.exports = function MembersApi({
validateMember,
updateMember,
getMember,
deleteMember,
listMembers,
sendEmail,
siteConfig
@ -35,6 +36,7 @@ module.exports = function MembersApi({
createMember,
updateMember,
getMember,
deleteMember,
validateMember,
sendEmail,
encodeToken,

View file

@ -66,4 +66,22 @@ module.exports = class PaymentProcessorService {
return this._processors[metadata.adapter].getSubscription(member, metadata);
});
}
removeSubscription(member, metadata) {
if (!metadata.adapter) {
return Promise.reject(new Error('removeSubscription(member, { adapter }) requires an adapter'));
}
return this._ready.then(() => {
return this._processors[metadata.adapter].removeSubscription(member, metadata);
});
}
removeCustomer(member, metadata) {
if (!metadata.adapter) {
return Promise.reject(new Error('removeCustomer(member, { adapter }) requires an adapter'));
}
return this._ready.then(() => {
return this._processors[metadata.adapter].removeCustomer(member, metadata);
});
}
};

View file

@ -118,6 +118,22 @@ function createCreator(resource, getAttrs) {
};
}
function createRemover(resource, get, generateHashSeed) {
return function remove(stripe, object, ...rest) {
return get(stripe, object, generateHashSeed(object, ...rest)).then((res) => {
return stripe[resource].del(res.id).then((result) => {
return result;
}, (err) => {
throw err;
});
}).catch((err) => {
if (err.code !== 'resource_missing') {
throw err;
}
});
};
}
function createEnsurer(get, create, generateHashSeed) {
return function ensure(stripe, object, ...rest) {
return get(stripe, object, generateHashSeed(object, ...rest))
@ -134,9 +150,10 @@ function createEnsurer(get, create, generateHashSeed) {
function createApi(resource, validResult, getAttrs, generateHashSeed) {
const get = createGetter(resource, validResult);
const create = createCreator(resource, getAttrs);
const remove = createRemover(resource, get, generateHashSeed);
const ensure = createEnsurer(get, create, generateHashSeed);
return {
get, create, ensure
get, create, remove, ensure
};
}

View file

@ -91,4 +91,24 @@ module.exports = class StripePaymentProcessor {
return api.subscriptions.get(this._stripe, member);
});
}
removeSubscription(member) {
if (!this._stripe) {
throw new Error('StripePaymentProcessor must be configured()');
}
return this._ready.then(() => {
return api.subscriptions.remove(this._stripe, member);
});
}
removeCustomer(member) {
if (!this._stripe) {
throw new Error('StripePaymentProcessor must be configured()');
}
return this._ready.then(() => {
return api.customers.remove(this._stripe, member);
});
}
};

View file

@ -4,6 +4,7 @@ module.exports = function ({
updateMember,
getMember,
listMembers,
deleteMember,
validateMember,
sendEmail,
encodeToken,
@ -45,12 +46,30 @@ module.exports = function ({
});
}
function destroy(...args) {
return getMember(...args).then((member) => {
if (!member) {
return null;
}
return subscriptions.getAdapters().then((adapters) => {
return Promise.all(adapters.map((adapter) => {
return subscriptions.removeCustomer(member, {
adapter
});
}));
}).then(() => {
return deleteMember(...args);
});
});
}
return {
requestPasswordReset,
resetPassword,
create: createMember,
validate: validateMember,
list: listMembers,
destroy,
get
};
};

View file

@ -37,6 +37,17 @@ function getMember(data, options) {
});
}
function deleteMember(options) {
options = options || {};
return models.Member.destroy(options).catch(models.Member.NotFoundError, () => {
throw new common.errors.NotFoundError({
message: common.i18n.t('errors.api.resource.resourceNotFound', {
resource: 'Member'
})
});
});
}
function listMembers(options) {
return models.Member.findPage(options).then((models) => {
return {
@ -170,6 +181,7 @@ const api = MembersApi({
validateAudience,
createMember,
getMember,
deleteMember,
listMembers,
validateMember,
updateMember,

View file

@ -109,6 +109,7 @@ module.exports = function apiRoutes() {
// ## Members
router.get('/members', shared.middlewares.labs.members, mw.authAdminApi, http(apiv2.members.browse));
router.get('/members/:id', shared.middlewares.labs.members, mw.authAdminApi, http(apiv2.members.read));
router.del('/members/:id', shared.middlewares.labs.members, mw.authAdminApi, http(apiv2.members.destroy));
// ## Roles
router.get('/roles/', mw.authAdminApi, http(apiv2.roles.browse));