mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Handled storing complimentary subscription expiry
refs https://github.com/TryGhost/Team/issues/1727 - if feature flag is enabled, handles storing expiry date on complimentary subscriptions in `expiry_at` column of `members_products` - updates the expiry value on both member edit or add with tiers - expiry is passed as `expiry_at` in `tiers` list of a member - includes `expiry_at` on tiers data of a member when flag is enabled
This commit is contained in:
parent
c123fdf5da
commit
1258156c38
4 changed files with 82 additions and 18 deletions
|
@ -3,6 +3,7 @@ const uuid = require('uuid');
|
|||
const _ = require('lodash');
|
||||
const config = require('../../shared/config');
|
||||
const {gravatar} = require('../lib/image');
|
||||
const labs = require('../../shared/labs');
|
||||
|
||||
const Member = ghostBookshelf.Model.extend({
|
||||
tableName: 'members',
|
||||
|
@ -102,12 +103,16 @@ const Member = ghostBookshelf.Model.extend({
|
|||
|
||||
products() {
|
||||
return this.belongsToMany('Product', 'members_products', 'member_id', 'product_id')
|
||||
.withPivot('sort_order')
|
||||
.withPivot('sort_order', 'expiry_at')
|
||||
.query('orderBy', 'sort_order', 'ASC')
|
||||
.query((qb) => {
|
||||
// avoids bookshelf adding a `DISTINCT` to the query
|
||||
// we know the result set will already be unique and DISTINCT hurts query performance
|
||||
qb.columns('products.*');
|
||||
if (labs.isSet('compExpiring')) {
|
||||
qb.columns('products.*', 'expiry_at');
|
||||
} else {
|
||||
qb.columns('products.*');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -155,6 +160,21 @@ const Member = ghostBookshelf.Model.extend({
|
|||
return this.hasMany('EmailRecipient', 'member_id', 'id');
|
||||
},
|
||||
|
||||
async updateTierExpiry(products = [], options = {}) {
|
||||
if (!labs.isSet('compExpiring')) {
|
||||
return;
|
||||
}
|
||||
for (const product of products) {
|
||||
if (product?.expiry_at) {
|
||||
const expiry = new Date(product.expiry_at);
|
||||
const queryOptions = _.extend({}, options, {
|
||||
query: {where: {product_id: product.id}}
|
||||
});
|
||||
await this.products().updatePivot({expiry_at: expiry}, queryOptions);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
serialize(options) {
|
||||
const defaultSerializedObject = ghostBookshelf.Model.prototype.serialize.call(this, options);
|
||||
|
||||
|
@ -344,7 +364,13 @@ const Member = ghostBookshelf.Model.extend({
|
|||
return this.add(data, Object.assign({transacting}, unfilteredOptions));
|
||||
});
|
||||
}
|
||||
return ghostBookshelf.Model.add.call(this, data, unfilteredOptions);
|
||||
|
||||
return ghostBookshelf.Model.add.call(this, data, unfilteredOptions).then(async (member) => {
|
||||
if (data.products) {
|
||||
await member.updateTierExpiry(data.products, _.pick(unfilteredOptions, 'transacting'));
|
||||
}
|
||||
return member;
|
||||
});
|
||||
},
|
||||
|
||||
edit(data, unfilteredOptions = {}) {
|
||||
|
@ -353,7 +379,13 @@ const Member = ghostBookshelf.Model.extend({
|
|||
return this.edit(data, Object.assign({transacting}, unfilteredOptions));
|
||||
});
|
||||
}
|
||||
return ghostBookshelf.Model.edit.call(this, data, unfilteredOptions);
|
||||
|
||||
return ghostBookshelf.Model.edit.call(this, data, unfilteredOptions).then(async (member) => {
|
||||
if (data.products) {
|
||||
await member.updateTierExpiry(data.products, _.pick(unfilteredOptions, 'transacting'));
|
||||
}
|
||||
return member;
|
||||
});
|
||||
},
|
||||
|
||||
destroy(unfilteredOptions = {}) {
|
||||
|
|
|
@ -63,7 +63,7 @@ exports[`Members API - With Newsletters - compat mode Can fetch members who are
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "2071",
|
||||
"content-length": "2088",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -218,7 +218,7 @@ exports[`Members API - With Newsletters - compat mode Can fetch members who are
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "11646",
|
||||
"content-length": "11697",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -289,7 +289,7 @@ exports[`Members API - With Newsletters Can fetch members who are NOT subscribed
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "2071",
|
||||
"content-length": "2088",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -444,7 +444,7 @@ exports[`Members API - With Newsletters Can fetch members who are subscribed 2:
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "11646",
|
||||
"content-length": "11697",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
|
|
@ -905,6 +905,7 @@ Object {
|
|||
"active": true,
|
||||
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"description": null,
|
||||
"expiry_at": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"monthly_price_id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "Default Product",
|
||||
|
@ -928,7 +929,7 @@ exports[`Members API Can add complimentary subscription (out of date) 4: [header
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "2583",
|
||||
"content-length": "2617",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -1125,7 +1126,7 @@ exports[`Members API Can browse 2: [headers] 1`] = `
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "13616",
|
||||
"content-length": "13684",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -1286,6 +1287,7 @@ Object {
|
|||
"active": true,
|
||||
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"description": null,
|
||||
"expiry_at": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"monthly_price_id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "Default Product",
|
||||
|
@ -1309,7 +1311,7 @@ exports[`Members API Can create a member with an existing complimentary subscrip
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "2634",
|
||||
"content-length": "2668",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
|
||||
|
@ -1378,7 +1380,7 @@ exports[`Members API Can create a member with an existing paid subscription 2: [
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "2620",
|
||||
"content-length": "2654",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
|
||||
|
@ -1440,6 +1442,7 @@ Object {
|
|||
"active": true,
|
||||
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"description": null,
|
||||
"expiry_at": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"monthly_price_id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"name": "Default Product",
|
||||
|
@ -1463,7 +1466,7 @@ exports[`Members API Can create a new member with a product (complementary) 2: [
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "2410",
|
||||
"content-length": "2444",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
|
||||
|
@ -1826,7 +1829,7 @@ exports[`Members API Can filter by paid status 2: [headers] 1`] = `
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "9935",
|
||||
"content-length": "10003",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -1939,7 +1942,7 @@ exports[`Members API Can filter on newsletter slug 2: [headers] 1`] = `
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "8642",
|
||||
"content-length": "8676",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -2144,7 +2147,7 @@ exports[`Members API Can filter on tier slug 2: [headers] 1`] = `
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "20695",
|
||||
"content-length": "20967",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -2328,7 +2331,7 @@ exports[`Members API Can ignore any unknown includes 2: [headers] 1`] = `
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "9935",
|
||||
"content-length": "10003",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -3494,7 +3497,7 @@ exports[`Members API Search for paid members retrieves member with email paid@te
|
|||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "2486",
|
||||
"content-length": "2503",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
const sinon = require('sinon');
|
||||
const should = require('should');
|
||||
const models = require('../../../../core/server/models');
|
||||
const configUtils = require('../../../utils/configUtils');
|
||||
const labs = require('../../../../core/shared/labs');
|
||||
|
||||
const config = configUtils.config;
|
||||
|
||||
|
@ -49,4 +51,31 @@ describe('Unit: models/member', function () {
|
|||
should(json.avatar_image).eql(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateTierExpiry', function () {
|
||||
let memberModel;
|
||||
let updatePivot;
|
||||
|
||||
beforeEach(function () {
|
||||
memberModel = new models.Member({email: 'text@example.com'});
|
||||
updatePivot = sinon.stub();
|
||||
|
||||
sinon.stub(memberModel, 'products').callsFake(() => {
|
||||
return {
|
||||
updatePivot: updatePivot
|
||||
};
|
||||
});
|
||||
sinon.stub(labs, 'isSet').returns(true);
|
||||
});
|
||||
|
||||
it('calls updatePivot on member products to set expiry', function () {
|
||||
const expiry = (new Date()).toISOString();
|
||||
memberModel.updateTierExpiry([{
|
||||
expiry_at: expiry,
|
||||
id: '1'
|
||||
}]);
|
||||
|
||||
updatePivot.calledWith({expiry_at: new Date(expiry)}, {query: {where: {product_id: '1'}}}).should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue