Allowed tiers include and data for member endpoints (#14790)

refs https://github.com/TryGhost/Team/issues/1145

- allows members endpoint to accept `?include=tiers`
- allows members endpoint to return `tiers` data
This commit is contained in:
Rishabh Garg 2022-05-11 22:26:03 +05:30 committed by GitHub
parent 409dc3b534
commit 3836030950
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 226 additions and 34 deletions

View File

@ -29,7 +29,7 @@ const messages = {
resourceNotFound: '{resource} not found.'
};
const allowedIncludes = ['email_recipients', 'products'];
const allowedIncludes = ['email_recipients', 'products', 'tiers'];
module.exports = {
docName: 'members',

View File

@ -16,6 +16,19 @@ function defaultRelations(frame) {
}
module.exports = {
all(_apiConfig, frame) {
if (!frame.options.withRelated) {
return;
}
frame.options.withRelated = frame.options.withRelated.map((relation) => {
if (relation === 'tiers') {
return 'products';
}
return relation;
});
},
browse(apiConfig, frame) {
debug('browse');
defaultRelations(frame);
@ -64,6 +77,11 @@ module.exports = {
}
});
}
if (frame.data.members[0].tiers) {
frame.data.members[0].products = frame.data.members[0].tiers;
}
defaultRelations(frame);
},

View File

@ -128,6 +128,7 @@ function serializeMember(member, options) {
if (json.products) {
serialized.products = json.products;
serialized.tiers = json.products;
}
if (labsService.isSet('multipleNewsletters')) {

View File

@ -52,6 +52,7 @@ Object {
"status": "free",
"subscribed": false,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -63,7 +64,7 @@ exports[`Members API Can add 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": "625",
"content-length": "636",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -94,6 +95,7 @@ Object {
"status": "free",
"subscribed": false,
"subscriptions": Array [],
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -105,7 +107,7 @@ exports[`Members API Can add a member that is not subscribed (old) 2: [headers]
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": "501",
"content-length": "512",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": Any<String>,
@ -173,6 +175,7 @@ Object {
"status": "active",
},
],
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -184,7 +187,7 @@ exports[`Members API Can add a subscription 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": "2055",
"content-length": "2066",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
@ -251,6 +254,7 @@ Object {
"status": "active",
},
],
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -262,7 +266,7 @@ exports[`Members API Can add a subscription 4: [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": "2055",
"content-length": "2066",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
@ -319,6 +323,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -330,7 +335,7 @@ exports[`Members API Can add and edit with custom newsletters 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": "1294",
"content-length": "1305",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -388,6 +393,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -399,7 +405,7 @@ exports[`Members API Can add and edit with custom newsletters 4: [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": "1293",
"content-length": "1304",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
@ -482,6 +488,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Array [],
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -493,7 +500,7 @@ exports[`Members API Can add and send a signup confirmation email (old) 2: [head
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": "1789",
"content-length": "1800",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": Any<String>,
@ -587,6 +594,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Array [],
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -598,7 +606,7 @@ exports[`Members API Can add and send a signup confirmation email 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": "1784",
"content-length": "1795",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": Any<String>,
@ -639,6 +647,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -650,7 +659,7 @@ exports[`Members API Can add complimentary subscription (out of date) 2: [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": "1103",
"content-length": "1114",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -681,6 +690,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -692,7 +702,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": "1103",
"content-length": "1114",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
@ -1046,6 +1056,7 @@ Object {
"status": "comped",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Any<Array>,
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -1057,7 +1068,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": "2433",
"content-length": "2802",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -1115,6 +1126,7 @@ Object {
"status": "paid",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Any<Array>,
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -1126,7 +1138,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": "2508",
"content-length": "2877",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -1199,6 +1211,22 @@ Object {
"status": "comped",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [
Object {
"active": true,
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"monthly_price_id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Default Product",
"slug": "default-product",
"type": "paid",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"visibility": "public",
"welcome_page_url": "/welcome-paid",
"yearly_price_id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
},
],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -1210,7 +1238,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": "2389",
"content-length": "2758",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -1251,6 +1279,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -1262,7 +1291,7 @@ exports[`Members API Can destroy 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": "1759",
"content-length": "1770",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -1333,6 +1362,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -1344,7 +1374,7 @@ exports[`Members API Can edit by id 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": "1121",
"content-length": "1132",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -1375,6 +1405,7 @@ Object {
"status": "free",
"subscribed": false,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -1386,7 +1417,7 @@ exports[`Members API Can edit by id 4: [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": "476",
"content-length": "487",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
@ -2294,6 +2325,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -2305,7 +2337,7 @@ exports[`Members API Can read 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": "1254",
"content-length": "1265",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
@ -2336,6 +2368,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -2347,7 +2380,49 @@ exports[`Members API Can read and include email_recipients 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": "1276",
"content-length": "1287",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Members API Can read and include tiers 1: [body] 1`] = `
Object {
"members": Array [
Object {
"avatar_image": null,
"comped": false,
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"email": "member1@test.com",
"email_count": 0,
"email_open_rate": null,
"email_opened_count": 0,
"geolocation": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"labels": Any<Array>,
"last_seen_at": null,
"name": "Mr Egg",
"newsletters": Any<Array>,
"note": null,
"products": Array [],
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Any<Array>,
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
],
}
`;
exports[`Members API Can read and include tiers 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": "1265",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
@ -2377,6 +2452,7 @@ Object {
"status": "free",
"subscribed": false,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -2388,7 +2464,7 @@ exports[`Members API Can subscribe by setting (old) subscribed property to true
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": "483",
"content-length": "494",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -2472,6 +2548,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -2483,7 +2560,7 @@ exports[`Members API Can subscribe by setting (old) subscribed property to true
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": "1773",
"content-length": "1784",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
@ -2513,6 +2590,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -2524,7 +2602,7 @@ exports[`Members API Can subscribe to a newsletter 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": "1111",
"content-length": "1122",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -2555,6 +2633,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -2566,7 +2645,7 @@ exports[`Members API Can subscribe to a newsletter 4: [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": "1116",
"content-length": "1127",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
@ -2765,6 +2844,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -2776,7 +2856,7 @@ exports[`Members API Can unsubscribe by setting (old) subscribed property to fal
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": "1129",
"content-length": "1140",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
@ -2807,6 +2887,7 @@ Object {
"status": "free",
"subscribed": false,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -2818,7 +2899,7 @@ exports[`Members API Can unsubscribe by setting (old) subscribed property to fal
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": "488",
"content-length": "499",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Origin, Accept-Encoding",
@ -3116,6 +3197,7 @@ Object {
"status": "free",
"subscribed": true,
"subscriptions": Any<Array>,
"tiers": Array [],
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
},
@ -3127,7 +3209,7 @@ exports[`Members API Subscribes to default newsletters 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": "1760",
"content-length": "1771",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/members\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,

View File

@ -14,7 +14,7 @@ const models = require('../../../core/server/models');
async function assertMemberEvents({eventType, memberId, asserts}) {
const events = await models[eventType].where('member_id', memberId).fetchAll();
const eventsJSON = events.map(e => e.toJSON());
// Order shouldn't matter here
for (const a of asserts) {
eventsJSON.should.matchAny(a);
@ -77,6 +77,11 @@ const memberMatcherShallowIncludes = {
newsletters: anyArray
};
const memberMatcherShallowIncludesWithTiers = {
...memberMatcherShallowIncludes,
tiers: anyArray
};
let agent;
describe('Members API without Stripe', function () {
@ -319,6 +324,18 @@ describe('Members API', function () {
});
});
it('Can read and include tiers', async function () {
await agent
.get(`/members/${testUtils.DataGenerator.Content.members[0].id}/?include=tiers`)
.expectStatus(200)
.matchBodySnapshot({
members: new Array(1).fill(memberMatcherShallowIncludesWithTiers)
})
.matchHeaderSnapshot({
etag: anyEtag
});
});
// Create a member
it('Can add', async function () {
@ -577,7 +594,7 @@ describe('Members API', function () {
const compedPayload = {
id: newMember.id,
email: newMember.email,
products: [
tiers: [
{
id: product.id
}
@ -592,6 +609,7 @@ describe('Members API', function () {
const updatedMember = body2.members[0];
assert.equal(updatedMember.status, 'comped', 'A comped member should have the comped status');
assert.equal(updatedMember.products.length, 1, 'The member should have one product');
assert.equal(updatedMember.tiers.length, 1, 'The member should have one product');
await assertMemberEvents({
eventType: 'MemberStatusEvent',
@ -631,7 +649,7 @@ describe('Members API', function () {
name: 'Name',
email: 'compedtest3@test.com',
newsletters: [newsletters[0]],
products: [
tiers: [
{
id: product.id
}
@ -645,13 +663,13 @@ describe('Members API', function () {
const newMember = body.members[0];
assert.equal(newMember.status, 'comped', 'The new member should have the comped status');
assert.equal(newMember.products.length, 1, 'The member should have 1 product');
assert.equal(newMember.tiers.length, 1, 'The member should have 1 product');
// Remove it
const removePayload = {
id: newMember.id,
email: newMember.email,
products: []
tiers: []
};
const {body: body2} = await agent
@ -662,6 +680,7 @@ describe('Members API', function () {
const updatedMember = body2.members[0];
assert.equal(updatedMember.status, 'free', 'The member should have the free status');
assert.equal(updatedMember.products.length, 0, 'The member should have 0 products');
assert.equal(updatedMember.tiers.length, 0, 'The member should have 0 products');
await assertMemberEvents({
eventType: 'MemberStatusEvent',
@ -704,7 +723,7 @@ describe('Members API', function () {
email: 'compedtest4@test.com',
subscribed: true,
newsletters: [newsletters[0]],
products: [
tiers: [
{
id: product.id
}
@ -730,6 +749,13 @@ describe('Members API', function () {
created_at: anyISODateTime,
updated_at: anyISODateTime
}),
tiers: new Array(1).fill({
id: anyObjectId,
monthly_price_id: anyObjectId,
yearly_price_id: anyObjectId,
created_at: anyISODateTime,
updated_at: anyISODateTime
}),
newsletters: new Array(1).fill(newsletterSnapshot)
})
})
@ -845,6 +871,7 @@ describe('Members API', function () {
labels: anyArray,
subscriptions: anyArray,
products: anyArray,
tiers: anyArray,
newsletters: new Array(1).fill(newsletterSnapshot)
})
})
@ -973,6 +1000,7 @@ describe('Members API', function () {
labels: anyArray,
subscriptions: anyArray,
products: anyArray,
tiers: anyArray,
newsletters: new Array(1).fill(newsletterSnapshot)
})
})
@ -1046,7 +1074,7 @@ describe('Members API', function () {
const readMember = readBody.members[0];
// Note that we explicitly need to ask to include products while browsing
const {body: browseBody} = await agent.get(`/members/?search=${memberWithPaidSubscription.email}&include=products`);
const {body: browseBody} = await agent.get(`/members/?search=${memberWithPaidSubscription.email}&include=tiers`);
assert.equal(browseBody.members.length, 1, 'The member was not found in browse');
const browseMember = browseBody.members[0];
@ -1229,7 +1257,7 @@ describe('Members API', function () {
const after = new Date();
after.setMilliseconds(0);
await agent
.put(`/members/${newMember.id}/`)
.body({members: [memberChanged]})

View File

@ -0,0 +1,19 @@
const should = require('should');
const serializers = require('../../../../../../../core/server/api/canary/utils/serializers');
describe('Unit: canary/utils/serializers/input/members', function () {
describe('all', function () {
it('converts tiers include', function () {
const apiConfig = {};
const frame = {
options: {
context: {},
withRelated: ['tiers']
}
};
serializers.input.members.all(apiConfig, frame);
should(frame.options.withRelated).containEql('products');
});
});
});

View File

@ -34,6 +34,23 @@ describe('Unit: canary/utils/serializers/output/members', function () {
should.exist(frame.response.members[0].newsletters);
});
it('browse: includes tiers data', function () {
const apiConfig = {docName: 'members'};
const frame = {
options: {
context: {}
}
};
const ctrlResponse = memberModel(testUtils.DataGenerator.forKnex.createMemberWithProducts());
memberSerializer.browse({
data: [ctrlResponse],
meta: null
}, apiConfig, frame);
should.exist(frame.response.members[0].tiers);
});
it('browse: removes newsletter data when flag is disabled', function () {
labsStub.returns(false);
const apiConfig = {docName: 'members'};
@ -77,4 +94,18 @@ describe('Unit: canary/utils/serializers/output/members', function () {
memberSerializer.read(ctrlResponse, apiConfig, frame);
should.not.exist(frame.response.members[0].newsletters);
});
it('read: includes tiers data', function () {
const apiConfig = {docName: 'members'};
const frame = {
options: {
context: {}
}
};
const ctrlResponse = memberModel(testUtils.DataGenerator.forKnex.createMemberWithProducts());
memberSerializer.read(ctrlResponse, apiConfig, frame);
should.exist(frame.response.members[0].tiers);
});
});

View File

@ -1015,6 +1015,18 @@ DataGenerator.forKnex = (function () {
});
}
function createMemberWithProducts(overrides) {
const newObj = _.cloneDeep(overrides);
return _.defaults(newObj, {
id: ObjectId().toHexString(),
email: 'member@ghost.org',
products: [{
id: 'product-1'
}]
});
}
function createLabel(overrides) {
const newObj = _.cloneDeep(overrides);
@ -1530,6 +1542,7 @@ DataGenerator.forKnex = (function () {
createToken,
createMember,
createMemberWithNewsletter,
createMemberWithProducts,
createLabel,
createMembersLabels,
createMembersStripeCustomer: createBasic,