Refactored member controller to use model layer

refs https://github.com/TryGhost/Members/pull/105

- As members module has become a core part it makes sense to follow the same principles as in all other controllers and use the model directly instead of calling external services.
- Bumped @tryghost/members-api to 0.11.1 . New stripe-specific methods used in controllers are available starting with this version
- Exposing these new methods is a little hacky because there are no relationships setup on members_* tables. Left notes for future improvements once relations are introduced.
- We don't allow for chaging member's emails at the moment. For this reason had to modify JSON schema a little. It doesn't support OO inheritence: "This shortcoming is perhaps one of the biggest surprises of the combining operations in JSON schema: it does not behave like inheritance in an object-oriented language. " (ref. https://json-schema.org/understanding-json-schema/reference/combining.html#allof)
This commit is contained in:
Nazar Gargol 2020-01-15 17:52:47 +07:00
parent f31a338797
commit e9e3f58792
4 changed files with 101 additions and 17 deletions

View File

@ -6,6 +6,28 @@ const membersService = require('../../services/members');
const common = require('../../lib/common');
const fsLib = require('../../lib/fs');
const listMembers = async function (options) {
const res = (await models.Member.findPage(options));
const members = res.data.map(model => model.toJSON(options));
// NOTE: this logic is here until relations between Members/MemberStripeCustomer/StripeCustomerSubscription
// are in place
const membersWithSubscriptions = await Promise.all(members.map(async function (member) {
const subscriptions = await membersService.api.members.getStripeSubscriptions(member);
return Object.assign(member, {
stripe: {
subscriptions
}
});
}));
return {
members: membersWithSubscriptions,
meta: res.meta
};
};
const members = {
docName: 'members',
browse: {
@ -19,8 +41,8 @@ const members = {
],
permissions: true,
validation: {},
query(frame) {
return membersService.api.members.list(frame.options);
async query(frame) {
return listMembers(frame.options);
}
},
@ -33,12 +55,24 @@ const members = {
validation: {},
permissions: true,
async query(frame) {
const member = await membersService.api.members.get(frame.data, frame.options);
let member = await models.Member.findOne(frame.data, frame.options);
if (!member) {
throw new common.errors.NotFoundError({
message: common.i18n.t('errors.api.members.memberNotFound')
});
}
// NOTE: this logic is here until relations between Members/MemberStripeCustomer/StripeCustomerSubscription
// are in place
const subscriptions = await membersService.api.members.getStripeSubscriptions(member);
member = member.toJSON(frame.options);
Object.assign(member, {
stripe: {
subscriptions
}
});
return member;
}
},
@ -63,10 +97,12 @@ const members = {
permissions: true,
async query(frame) {
try {
const member = await membersService.api.members.create(frame.data.members[0], {
sendEmail: frame.options.send_email,
emailType: frame.options.email_type
});
const member = await models.Member.add(frame.data.members[0], frame.options);
if (frame.options.send_email) {
await membersService.api.sendEmailWithMagicLink(member.get('email'), frame.options.email_type);
}
return member;
} catch (error) {
if (error.code && error.message.toLowerCase().indexOf('unique') !== -1) {
@ -93,7 +129,8 @@ const members = {
},
permissions: true,
async query(frame) {
const member = await membersService.api.members.update(frame.data.members[0], frame.options);
const member = await models.Member.edit(frame.data.members[0], frame.options);
return member;
}
},
@ -114,7 +151,21 @@ const members = {
permissions: true,
async query(frame) {
frame.options.require = true;
await membersService.api.members.destroy(frame.options)
let member = await models.Member.findOne(frame.data, frame.options);
if (!member) {
throw new common.errors.NotFoundError({
message: common.i18n.t('errors.api.resource.resourceNotFound', {
resource: 'Member'
})
});
}
// NOTE: move to a model layer once Members/MemberStripeCustomer relations are in place
await membersService.api.members.destroyStripeSubscriptions(member);
await models.Member.destroy(frame.options)
.catch(models.Member.NotFoundError, () => {
throw new common.errors.NotFoundError({
message: common.i18n.t('errors.api.resource.resourceNotFound', {
@ -147,8 +198,8 @@ const members = {
method: 'browse'
},
validation: {},
query(frame) {
return membersService.api.members.list(frame.options);
async query(frame) {
return listMembers(frame.options);
}
},

View File

@ -13,7 +13,40 @@
"maxItems": 1,
"items": {
"type": "object",
"allOf": [{"$ref": "members#/definitions/member"}]
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"maxLength": 191,
"pattern": "^([^,]|$)"
},
"note": {
"type": "string",
"minLength": 0,
"maxLength": 2000
},
"subscribed": {
"type": "boolean"
},
"id": {
"strip": true
},
"email": {
"strip": true
},
"created_at": {
"strip": true
},
"created_by": {
"strip": true
},
"updated_at": {
"strip": true
},
"updated_by": {
"strip": true
}
}
}
}
},

View File

@ -41,7 +41,7 @@
"dependencies": {
"@nexes/nql": "0.3.0",
"@tryghost/helpers": "1.1.20",
"@tryghost/members-api": "0.11.0",
"@tryghost/members-api": "0.11.1",
"@tryghost/members-ssr": "0.7.3",
"@tryghost/social-urls": "0.1.5",
"@tryghost/string": "^0.1.3",

View File

@ -237,10 +237,10 @@
jsonwebtoken "^8.5.1"
lodash "^4.17.15"
"@tryghost/members-api@0.11.0":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-0.11.0.tgz#94a6ca7b11bf5132b7c8102dc92501965bf2e766"
integrity sha512-WV+8y1TzYnvoZGnw9kXH8hMuJhgebHbOskeJiy8X8yn45UD/hlMMuF+y015W1lrMYblEhBS5E4eGfAX+laKqOw==
"@tryghost/members-api@0.11.1":
version "0.11.1"
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-0.11.1.tgz#c878ea23b46eeede101715d2ecc72df8f31f307a"
integrity sha512-SeT66Ddkfq2PQIt9XGn+/qcgc2tRMShgFIuGcE01Df/b27WfkELQu8IP+CK/1giuXg/cooEdpoWmbLQ5qm/+3w==
dependencies:
"@tryghost/magic-link" "^0.3.3"
bluebird "^3.5.4"