2019-08-09 17:11:24 +03:00
|
|
|
// NOTE: We must not cache references to membersService.api
|
|
|
|
// as it is a getter and may change during runtime.
|
2019-10-03 20:59:19 +03:00
|
|
|
const Promise = require('bluebird');
|
2019-12-06 08:04:10 +03:00
|
|
|
const models = require('../../models');
|
2019-08-09 17:11:24 +03:00
|
|
|
const membersService = require('../../services/members');
|
2019-09-03 07:10:32 +03:00
|
|
|
const common = require('../../lib/common');
|
2019-10-03 20:59:19 +03:00
|
|
|
const fsLib = require('../../lib/fs');
|
2019-08-09 17:11:24 +03:00
|
|
|
|
2020-01-28 07:25:00 +03:00
|
|
|
const decorateWithSubscriptions = async function (member) {
|
2020-01-15 13:52:47 +03:00
|
|
|
// NOTE: this logic is here until relations between Members/MemberStripeCustomer/StripeCustomerSubscription
|
|
|
|
// are in place
|
2020-01-28 07:25:00 +03:00
|
|
|
const subscriptions = await membersService.api.members.getStripeSubscriptions(member);
|
2020-01-15 13:52:47 +03:00
|
|
|
|
2020-01-28 07:25:00 +03:00
|
|
|
return Object.assign(member, {
|
|
|
|
stripe: {
|
|
|
|
subscriptions
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const listMembers = async function (options) {
|
|
|
|
const res = (await models.Member.findPage(options));
|
|
|
|
const memberModels = res.data.map(model => model.toJSON(options));
|
|
|
|
|
|
|
|
const members = await Promise.all(memberModels.map(async function (member) {
|
|
|
|
return decorateWithSubscriptions(member);
|
2020-01-15 13:52:47 +03:00
|
|
|
}));
|
|
|
|
|
|
|
|
return {
|
2020-01-28 07:25:00 +03:00
|
|
|
members: members,
|
2020-01-15 13:52:47 +03:00
|
|
|
meta: res.meta
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2019-08-09 17:11:24 +03:00
|
|
|
const members = {
|
|
|
|
docName: 'members',
|
|
|
|
browse: {
|
|
|
|
options: [
|
|
|
|
'limit',
|
|
|
|
'fields',
|
|
|
|
'filter',
|
|
|
|
'order',
|
|
|
|
'debug',
|
|
|
|
'page'
|
|
|
|
],
|
|
|
|
permissions: true,
|
|
|
|
validation: {},
|
2020-01-15 13:52:47 +03:00
|
|
|
async query(frame) {
|
|
|
|
return listMembers(frame.options);
|
2019-08-09 17:11:24 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
read: {
|
|
|
|
headers: {},
|
|
|
|
data: [
|
|
|
|
'id',
|
|
|
|
'email'
|
|
|
|
],
|
|
|
|
validation: {},
|
|
|
|
permissions: true,
|
2019-09-03 07:10:32 +03:00
|
|
|
async query(frame) {
|
2020-01-28 07:25:00 +03:00
|
|
|
let model = await models.Member.findOne(frame.data, frame.options);
|
2020-01-15 13:52:47 +03:00
|
|
|
|
2020-01-28 07:25:00 +03:00
|
|
|
if (!model) {
|
2019-09-03 07:10:32 +03:00
|
|
|
throw new common.errors.NotFoundError({
|
|
|
|
message: common.i18n.t('errors.api.members.memberNotFound')
|
|
|
|
});
|
|
|
|
}
|
2020-01-15 13:52:47 +03:00
|
|
|
|
2020-01-28 07:25:00 +03:00
|
|
|
const member = model.toJSON(frame.options);
|
2020-01-15 13:52:47 +03:00
|
|
|
|
2020-01-28 07:25:00 +03:00
|
|
|
return decorateWithSubscriptions(member);
|
2019-08-09 17:11:24 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-10-03 12:15:50 +03:00
|
|
|
add: {
|
|
|
|
statusCode: 201,
|
|
|
|
headers: {},
|
|
|
|
options: [
|
|
|
|
'send_email',
|
|
|
|
'email_type'
|
|
|
|
],
|
|
|
|
validation: {
|
|
|
|
data: {
|
|
|
|
email: {required: true}
|
|
|
|
},
|
|
|
|
options: {
|
|
|
|
email_type: {
|
|
|
|
values: ['signin', 'signup', 'subscribe']
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
permissions: true,
|
2019-10-09 10:14:26 +03:00
|
|
|
async query(frame) {
|
|
|
|
try {
|
2020-01-28 07:25:00 +03:00
|
|
|
const model = await models.Member.add(frame.data.members[0], frame.options);
|
2020-01-15 13:52:47 +03:00
|
|
|
|
|
|
|
if (frame.options.send_email) {
|
2020-01-28 07:25:00 +03:00
|
|
|
await membersService.api.sendEmailWithMagicLink(model.get('email'), frame.options.email_type);
|
2020-01-15 13:52:47 +03:00
|
|
|
}
|
|
|
|
|
2020-01-28 07:25:00 +03:00
|
|
|
const member = model.toJSON(frame.options);
|
|
|
|
|
|
|
|
return decorateWithSubscriptions(member);
|
2019-10-09 10:14:26 +03:00
|
|
|
} catch (error) {
|
|
|
|
if (error.code && error.message.toLowerCase().indexOf('unique') !== -1) {
|
|
|
|
throw new common.errors.ValidationError({message: common.i18n.t('errors.api.members.memberAlreadyExists')});
|
|
|
|
}
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
}
|
2019-10-03 12:15:50 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-10-03 14:38:22 +03:00
|
|
|
edit: {
|
|
|
|
statusCode: 200,
|
|
|
|
headers: {},
|
|
|
|
options: [
|
|
|
|
'id'
|
|
|
|
],
|
|
|
|
validation: {
|
|
|
|
options: {
|
|
|
|
id: {
|
|
|
|
required: true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
permissions: true,
|
|
|
|
async query(frame) {
|
2020-01-28 07:25:00 +03:00
|
|
|
const model = await models.Member.edit(frame.data.members[0], frame.options);
|
|
|
|
|
|
|
|
const member = model.toJSON(frame.options);
|
|
|
|
|
|
|
|
const subscriptions = await membersService.api.members.getStripeSubscriptions(member);
|
|
|
|
const compedSubscriptions = subscriptions.filter(sub => (sub.plan.nickname === 'Complimentary'));
|
|
|
|
|
|
|
|
if (frame.data.members[0].comped !== undefined && (frame.data.members[0].comped !== compedSubscriptions)) {
|
|
|
|
const hasCompedSubscription = !!(compedSubscriptions.length);
|
|
|
|
|
|
|
|
if (frame.data.members[0].comped && !hasCompedSubscription) {
|
|
|
|
await membersService.api.members.setComplimentarySubscription(member);
|
|
|
|
} else if (!(frame.data.members[0].comped) && hasCompedSubscription) {
|
|
|
|
await membersService.api.members.cancelComplimentarySubscription(member);
|
|
|
|
}
|
|
|
|
}
|
2020-01-15 13:52:47 +03:00
|
|
|
|
2020-01-28 07:25:00 +03:00
|
|
|
return decorateWithSubscriptions(member);
|
2019-10-03 14:38:22 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-09 17:11:24 +03:00
|
|
|
destroy: {
|
|
|
|
statusCode: 204,
|
|
|
|
headers: {},
|
|
|
|
options: [
|
|
|
|
'id'
|
|
|
|
],
|
|
|
|
validation: {
|
|
|
|
options: {
|
|
|
|
id: {
|
|
|
|
required: true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
permissions: true,
|
2019-10-02 11:25:49 +03:00
|
|
|
async query(frame) {
|
2019-08-09 17:11:24 +03:00
|
|
|
frame.options.require = true;
|
2020-01-15 13:52:47 +03:00
|
|
|
|
|
|
|
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)
|
2019-12-06 08:04:10 +03:00
|
|
|
.catch(models.Member.NotFoundError, () => {
|
|
|
|
throw new common.errors.NotFoundError({
|
|
|
|
message: common.i18n.t('errors.api.resource.resourceNotFound', {
|
|
|
|
resource: 'Member'
|
|
|
|
})
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-10-02 11:25:49 +03:00
|
|
|
return null;
|
2019-08-09 17:11:24 +03:00
|
|
|
}
|
2019-10-03 20:59:19 +03:00
|
|
|
},
|
|
|
|
|
2019-10-03 21:36:22 +03:00
|
|
|
exportCSV: {
|
2019-10-29 07:50:32 +03:00
|
|
|
options: [
|
|
|
|
'limit'
|
|
|
|
],
|
2019-10-03 21:36:22 +03:00
|
|
|
headers: {
|
|
|
|
disposition: {
|
|
|
|
type: 'csv',
|
|
|
|
value() {
|
|
|
|
const datetime = (new Date()).toJSON().substring(0, 10);
|
|
|
|
return `members.${datetime}.csv`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
response: {
|
|
|
|
format: 'plain'
|
|
|
|
},
|
|
|
|
permissions: {
|
|
|
|
method: 'browse'
|
|
|
|
},
|
|
|
|
validation: {},
|
2020-01-15 13:52:47 +03:00
|
|
|
async query(frame) {
|
|
|
|
return listMembers(frame.options);
|
2019-10-03 21:36:22 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-10-03 20:59:19 +03:00
|
|
|
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,
|
2019-10-10 07:51:27 +03:00
|
|
|
columnsToExtract: [{name: 'email', lookup: /email/i}, {name: 'name', lookup: /name/i}, {name: 'note', lookup: /note/i}]
|
2019-10-03 20:59:19 +03:00
|
|
|
}).then((result) => {
|
|
|
|
return Promise.all(result.map((entry) => {
|
|
|
|
const api = require('./index');
|
|
|
|
|
2019-10-09 10:14:26 +03:00
|
|
|
return Promise.resolve(api.members.add.query({
|
2019-10-03 20:59:19 +03:00
|
|
|
data: {
|
|
|
|
members: [{
|
|
|
|
email: entry.email,
|
2019-10-10 07:51:27 +03:00
|
|
|
name: entry.name,
|
|
|
|
note: entry.note
|
2019-10-03 20:59:19 +03:00
|
|
|
}]
|
|
|
|
},
|
|
|
|
options: {
|
|
|
|
context: frame.options.context,
|
|
|
|
options: {send_email: false}
|
|
|
|
}
|
2019-10-09 10:14:26 +03:00
|
|
|
})).reflect();
|
2019-10-03 20:59:19 +03:00
|
|
|
})).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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
2019-08-09 17:11:24 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = members;
|